Handle DragDrop outside of control - DragEnd event? - c#

i'm are trying to mimic the behavior of web browsers in a WinForm application where you can drag and drop tabs in and out of the browser and create another instance of the it when you drop the tab somewhere with no existing tabs.
Currently the WinForm application only has one main TabControl and I was looking at the DoDragDrop() related events, but they seem to only work when you have two TabControls and move TabPages around those two.
Is there a way to make it work with only one TabControl? Meaning, If you Drop a TabPage out of the TabControl then it will create a new TabControl with the TabPage in it?
I can only think of using:
private void TabControl_DragLeave(object sender, EventArgs e)
{
Form newInstance = new Form();
TabControl newTabControl = new TabControl();
newInstance.Controls.Add(newTabControl);
newTabControl.TabPages.Add(sender as TabPage);
newInstance.Show();
}
but that is pretty crud and will create the new tab every time you leave the TabControl.

It seems you are looking for an event which raises at the end of drop, regardless of ending over your control or outside of the control.
You can rely on QueryContinueDrag and check if the action is Drop, then check the mouse position and for example if it's not inside your control, just create another window and add the selected tab into a tab control inside the new window.
private void tabControl1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
tabControl1.DoDragDrop(tabControl1.SelectedTab, DragDropEffects.All);
}
}
private void tabControl1_DragOver(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(TabPage)))
e.Effect = DragDropEffects.Move;
}
private void tabControl1_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (e.Action == DragAction.Drop)
{
var tabPage = tabControl1.SelectedTab;
if (!tabControl1.RectangleToScreen(tabControl1.Bounds).Contains(Cursor.Position))
{
var form = new Form();
form.Text = tabPage.Text;
var tabControl = new TabControl();
tabControl.TabPages.Add(tabPage);
tabControl.Dock = DockStyle.Fill;
form.Controls.Add(tabControl);
form.FormBorderStyle = FormBorderStyle.SizableToolWindow;
form.StartPosition = FormStartPosition.Manual;
form.Location = new Point(Cursor.Position.X - form.Width / 2,
Cursor.Position.Y - SystemInformation.CaptionHeight / 2);
form.Show();
e.Action = DragAction.Cancel;
//You can comment tabControl.TabPages.Add
//Then set e.Action = DragAction.Continue
//Then the DragDrop event will raise and add the tab there.
}
}
}
private void tabControl1_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(TabPage)))
{
var tabPage = (TabPage)e.Data.GetData(typeof(TabPage));
tabControl1.TabPages.Remove(tabPage);
tabControl1.TabPages.Add(tabPage);
}
}
For more advanced scenarios and to enhance the code:
When start dragging, you can start dragging just if the mouse dragged at least for a specific points, for example 16 points. It's easy to calculate. Having p1 as mouse down point and p2 as mouse move point, and d as drag threshold. start dragging just in case (p1.X-p2.X)*(p1.X-p2.X) + (p1.Y-p2.Y)*(p1.Y-p2.Y) > d*d.
You can use GiveFeedback event to disable default cursor of the mouse and instead show a more suitable cursor while dragging, easily by e.UseDefaultCursors = false; and setting Cursor.Current = Cursors.SizeAll; for example.
You can encapsulate the logic and put it in a derived TabControl. Then in DragEnter and DragLeave events set a static property for tracking drop target. In case the drop-target has value, it means you are dropping on a derived tab control, otherwise it means you are dropping outside. Then drag and drop will be easily enabled for all your custom tab controls.
You can close the tool form, after a drag and drop, in case the form doesn't contain any other tab.
When adding the tab, you can insert it before/after the selected tab or the tab under cursor in target.

Related

How to add custom control at Run time in c#

i have one list box which has button, Textbox, Label. During runtime i will drag and drop the item from the list box, according to the selection the dynamic control will be created. (For example, If i select and drag Button from list box and drop it on the Windows Form, the button will be created). As same as i have created a CustomControl for Button. how can i add it in my list box at runtime? i mean while i drag and drop the button from the listbox the custom button should be generated. How to Do it?
Did You try this ?
var list = new ListBox();
list.Controls.Add(new Button());
If You need to dynamically create a class at runtime - take a look at this SF article How to dynamically create a class in C#?
For drag drop, you will need to set up 3 events:
A mouse down event on the list box to trigger the dragging:
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
listBox1.DoDragDrop(listBox1.SelectedItem, DragDropEffects.Copy);
}
A drag enter event on your form (or panel in this example):
private void panel1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
{
e.Effect = DragDropEffects.Copy;
}
}
And finally the drop event on the form/panel:
private void panel1_DragDrop(object sender, DragEventArgs e)
{
Control newControl = null;
// you would really use a better design pattern to do this, but
// for demo purposes I'm using a switch statement
string selectedItem = e.Data.GetData(DataFormats.Text) as string;
switch (selectedItem)
{
case "My Custom Control":
newControl = new CustomControl();
newControl.Location = panel1.PointToClient(new Point(e.X, e.Y));
newControl.Size = new System.Drawing.Size(75, 23);
break;
}
if (newControl != null) panel1.Controls.Add(newControl);
}
For this to work you must set "AllowDrop" to true on the target form/panel.
Use #Marty's answer to add the custom control to the listbox. Override the ToString() for a better description. There are so many ways of doing it. The important part is deciding what data type the list items will be and making sure that the correct type name is used in the e.Data.GetDataPresent method. e.Data.GetFormats() can help determine what name to use.

ContextMenuStrip not closing once clicked

I am creating a Context Menu Strip once a Rich Text Box is right clicked. There are 2 options, one to change the font and one to change the background color. However, once I click one of the menu options, the Context Menu Strip doesn't close and overlays dialogs that are displayed. I know I can make it "global" and force it to close, but I would rather not. What is the best way to handle this?
// If the console is right clicked then show font options
private void rtb_console_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
ContextMenuStrip menu = new ContextMenuStrip();
menu.Items.Add("Change Font");
menu.Items.Add("Change Background Color");
menu.Show(this, new Point(e.X, e.Y));
menu.ItemClicked += new ToolStripItemClickedEventHandler(menu_ItemClicked_ChangeFont);
}
}
// Determine whether to change the font or the font background color
void menu_ItemClicked_ChangeFont(object sender, ToolStripItemClickedEventArgs e)
{
Application.DoEvents(); // Read that this might help, but it doesn't
if (e.ClickedItem.Text == "Change Font")
{
FontDialog font = new FontDialog();
font.ShowColor = true;
font.Font = rtb_console.Font;
font.Color = rtb_console.ForeColor;
if (font.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
rtb_console.Font = font.Font;
rtb_console.ForeColor = font.Color;
}
}
else if (e.ClickedItem.Text == "Change Background Color")
{
ColorDialog color = new ColorDialog();
color.Color = rtb_console.BackColor;
if (color.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
rtb_console.BackColor = color.Color;
}
}
}
So this is what happens:
You don't want to create the ContextMenuStrip and show it manually each time. The better way to do this is to create the ContextMenuStrip once. Then assign it for the RichTextBox by assigning it to the ContextMenuStrip property of the RichTextBox. Doing this, you will no longer need to manually launch the ContextMenuStrip everytime a user clicks on it. It will occur automagically. It will also hide itself automagically in the way you would expect upon clicking on it.
Do this once and then remove your event handler for the MouseUp event:
ContextMenuStrip menu = new ContextMenuStrip();
menu.Items.Add("Change Font");
menu.Items.Add("Change Background Color");
menu.ItemClicked += new ToolStripItemClickedEventHandler(menu_ItemClicked_ChangeFont);
rtb_console.ContextStripMenu = menu;
Also, please please please don't use Application.DoEvents(); to try and force the UI to update itself. Head over to here and read the top answer. In general, if you are using Application.DoEvents(), you are doing something wrong and should considering changing your approach.
One thing you may also consider doing, but it is really just a matter of preference... If you are using Visual Studio, consider creating you ContextMenuStrip in the designer. That way, you can add your items, icons, and individual callbacks for each item very easily and visually. Just something I like to do out of pure personal preference.
In ToolStripItemClickedEventArgs just declare:
ContextMenuStrip menu = (ContextMenuStrip)(Sender)
if (e.ClickedItem.Text == "Change Font")
{
menu.hide();
/* and your code here*/...
}

"Unselect" event for MenuItem

I'm trying to implement a 'preview' scenario when the user hover a menu item.
For example, lets say a program has a context-menu with 'Set Color' sub-menu.
The sub-menu pop a list of color to choose from.
Now, when the mouse cursor is over a specific color, I want it to change a label of "Selected color".
And when the mouse cursor leave the selected color menu-item, I want to label to restore its original text.
The following code demonstrate changing the label when menu-item selected - mouse is over.
private void Init()
{
var mnuContextMenu = new ContextMenu();
this.ContextMenu = mnuContextMenu;
var smthingElseMenu = new MenuItem("Do something else");
var setColorMenu = new MenuItem("Set Color");
var colorBlue = new MenuItem("Blue");
var colorRed = new MenuItem("Red");
var colorGreen = new MenuItem("Green");
mnuContextMenu.MenuItems.Add(smthingElseMenu);
mnuContextMenu.MenuItems.Add(setColorMenu);
setColorMenu.MenuItems.Add(colorBlue);
setColorMenu.MenuItems.Add(colorRed);
setColorMenu.MenuItems.Add(colorGreen);
colorBlue.Select += ColorSelect;
colorRed.Select += ColorSelect;
colorGreen.Select += ColorSelect;
}
void ColorSelect(object sender, EventArgs e)
{
lblSelectedColor.Text = ((MenuItem) sender).Text;
}
But I couldn't find a way to make the label text restore when the mouse cursor leave the menu-item.
Any ideas how can I implement some kind of 'Unselect'/'MouseLeave' event for MenuItem?
There's no "un-select" event for MenuItems, unfortunately.
I would just catch the Collapse event of your context menu, and reset your label there. This would have the added benefit that if your user hovers over the "Red" option, then hovers off the context menu, the label should stay red until the context menu closes.
mnuContextMenu.Collapse += (s, e) => lblSelectedColor.Text = "None";
If you really need it to reset the label when your mouse leave the context menu, then you could catch the MouseEnter event of the Panel (or whatever) you have that surrounds the ContextMenu.
MyPanel.MouseEnter += (s, e) => lblSelectedColor.Text = "None";
EDIT Do consider using the ContextMenuStrip class instead. The ToolSTripMenuItem class has a MouseLeave event. And a Checked property, probably what you really want.
Can't you just save the old MenuItem ref.
private MenuItem _oldMenuItem;
void ColorSelect(object sender, EventArgs e)
{
if(_oldMenuItem != null) _oldMenuItem.Text = someText;
_oldMenuItem = sender as MenuItem;
lblSelectedColor.Text = ((MenuItem) sender).Text;
}
Use MouseEnter and MouseLeave events to handle everything. First is raised when when the mouse pointer enters the bounds of this element. And second mouse pointer leaves the bounds - at this point you restore default label text.
EDIT like Hans pointed in comment use ContextMenuStrip and ToolStripMenuItems and youll have those events.
Can't you use:
private void colorBlue_MouseEnter(object sender, EventArgs e)
{
// use color blue
}
private void colorBlue_MouseLeave(object sender, EventArgs e)
{
// use old color
}

How to allow user to drag a dynamically created control at the location of his choice

I am creating an application where I need to generate dynamically created controls say textbox or label etc.
Now what I that user can relocate that textbox to his desired location. Like we do in Visual Studio.
One way is to get new location by getting values from him using textbox. But I want the user interface easy.
Can we have such functionality in winforms
I have created a simple form that demonstrate how to move the control by dragging the control.
The example assumes there is a button named button1 on the form attached to the relevant event handler.
private Control activeControl;
private Point previousLocation;
private void button1_Click(object sender, EventArgs e)
{
var textbox = new TextBox();
textbox.Location = new Point(50, 50);
textbox.MouseDown += new MouseEventHandler(textbox_MouseDown);
textbox.MouseMove += new MouseEventHandler(textbox_MouseMove);
textbox.MouseUp += new MouseEventHandler(textbox_MouseUp);
this.Controls.Add(textbox);
}
void textbox_MouseDown(object sender, MouseEventArgs e)
{
activeControl = sender as Control;
previousLocation = e.Location;
Cursor = Cursors.Hand;
}
void textbox_MouseMove(object sender, MouseEventArgs e)
{
if (activeControl == null || activeControl != sender)
return;
var location = activeControl.Location;
location.Offset(e.Location.X - previousLocation.X, e.Location.Y - previousLocation.Y);
activeControl.Location = location;
}
void textbox_MouseUp(object sender, MouseEventArgs e)
{
activeControl = null;
Cursor = Cursors.Default;
}
It is the easiest way: First go to your solution name and right click. Select "Manage NuGet Packages". After a while a window opens with a search bar on top of it. Choose the "Browse option " and search DraggableControl package. The name Control.Draggable must be seen. Click on it then click install. Now you can use it's special commands for example.
private void button1_Click(object sender, EventArgs e)
{ Point p = new Point(20,70 * i);
RichTextBox tb = new RichTextBox();
tb.Location = p;
tb.Height= 60;
tb.Width = 100;
tb.Font = Normal;
ControlExtension.Draggable(tb,true);
this.Controls.Add(tb);
i++;
The ControlExtension.Draggable command can set it to be draggable or not. Just write the name of the object in the brackets (tb for me) and write a comma. Then write it is draggable (true) or not (false).
NOTE: Do not Forget to put a semicolon.
Hope it helps.
Link : https://www.nuget.org/packages/Control.Draggable/
You can call DoDragDrop with a data object containing or representing the control to begin a drag&drop operation, then handle the container's DragDrop event and move the control.
If you want to see the control as it's dragged, you can either make a transparent (handle WM_NCHITTEST) form under the mouse showing the control (call DrawToBitmap), or not use drag&drop at all and instead handle mouse events and track state manually.
If you want Visual Studio-style snaplines, you can compare the control's bounds to other controls, make a set of lines to draw, and draw them in a paint event.

Catching mouse move

Situation :
I'm currently working a project where the aim is to develop a VS-like IDE, where users can drap/drop new controls to a design surface, and modify properties of these controls.
So i implemented IDesignerHost, IServiceContainer, IContainer, IComponentChangeService, and some others useful interface, made to design that.
Everything works fine, i've my toolbox, my design surface, and my propertyGrid working just fine.
Problem is :
Attached to the drag'n'droped controls is a label, which has to follow the control while the user move it with his mouse.
I tried to use the LocationChanged event of the controls, to move the label when the control move. But this event occurs only one time, after the control has moved so the label doesn't move while the control move.
I'm not able to find a way for make this work. Does anyone have any good ideas please ?
Thank you
Edit :
I use a custom class, implementing IDesignerHost. Controls on this design surface doesn't fire events Mouse----- (e.g. : MouseDown, MouseMove).
I finally found how to do it :
I implemented ISelectionService and in the SetSelectedComponents function, I managed to select the label control associated with any selected control.
I overrided the designer of the label, so that no border/resize-rectangle would show when the label is selected.
This is a not very elegant solution, but it works well =).
Every Control has a ControlDesigner, provides additional methods to support extending and altering the behavior of an associated Control at design time.
In the ControlDesigner, you have a BehaviorService, which is responsible to control on DesignSurface behavior of the control.
BehaviorService has multiple Glyphs and Adorners which are like UI decorators for the control. The control re-size rubber-band like rectangle is a Glyph, called System.Windows.Forms.Design.Behavior.SelectionBorderGlyph a private class to .Net 2.0.
This links might be of help:
http://msdn.microsoft.com/en-us/library/ms171820.aspx
http://msdn.microsoft.com/en-us/library/bb514670%28VS.90%29.aspx
You should be able to add your custom Glyph which has a Label attached with the Control.
HTH
Form2 contains a panel1, label1
panel1.MouseMove += panel1_MouseMove
panel1.MouseDown += panel1_MouseDown
when MouseDown+Left Button clicked -> save initial mouse position
when MouseMove+Left Button clicked -> move (panel1+label1) by the difference between current mouse position and the saved initial position.
it's done.
public partial class Form2 : Form
{
private int _x, _y;
public Form2()
{
InitializeComponent();
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
panel1.Location = new Point(panel1.Location.X + (e.X - _x), panel1.Location.Y + (e.Y - _y));
label1.Location = new Point(label1.Location.X + (e.X - _x), label1.Location.Y + (e.Y - _y));
}
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
_x = e.X;
_y = e.Y;
}
}
}

Categories

Resources