I'm trying to build a simple interface that allows users to drop files into a listBox to add them to a process, and to drag them out to remove them. Everything is working fine, but I'd like to add one feature to make it just a tad more sophisticated.
Right now, I have the removal of the item tied to the DragLeave event, which means that as soon as the mouse leaves the box, the item is removed. But I'd like for users to be able to change their minds. In other words, if they realize they're dragging the wrong file out, I'd like them to be able to move the mouse back into the listBox and release the mouse to cancel the action. I'm thinking that means I need to be able to capture the MouseUp event instead of the DragLeave event. But that hasn't been successful so far.
Below is the code I'm currently using for removing files dragged out. How can I modify to keep the files from being removed form the list until the user lets the mouse button go?
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (listBox1.Items.Count == 0)
{
return;
}
int index = listBox1.IndexFromPoint(e.X, e.Y);
string s = listBox1.Items[index].ToString();
DragDropEffects dde1 = DoDragDrop(s, DragDropEffects.All);
}
private void listBox1_DragLeave(object sender, EventArgs e)
{
ListBox lb = sender as ListBox;
lb.Items.Remove(lb.SelectedItem);
}
Edit 2013/05/16
The comments and answers so far have been useful, but I realize my question isn't clear enough. In this case, I'm displaying a dialog separate from the parent form that is basically as big as the listBox. When someone drags a file out of the list, they're dragging it off the form completely. Have I backed myself into a corner by doing this? I recognize I'm making it harder than it has to be, but I'd still like to see how it would work if it's possible.
Here's a fairly quick hack approach to gaining the functionality you want:
public object lb_item = null;
private void listBox1_DragLeave(object sender, EventArgs e)
{
ListBox lb = sender as ListBox;
lb_item = lb.SelectedItem;
lb.Items.Remove(lb.SelectedItem);
}
private void listBox1_DragEnter(object sender, DragEventArgs e)
{
if (lb_item != null)
{
listBox1.Items.Add(lb_item);
lb_item = null;
}
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
lb_item = null;
if (listBox1.Items.Count == 0)
{
return;
}
int index = listBox1.IndexFromPoint(e.X, e.Y);
string s = listBox1.Items[index].ToString();
DragDropEffects dde1 = DoDragDrop(s, DragDropEffects.All);
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
lb_item = null;
}
Every time the user drags an item out of the box, it's saved temporarily until the user drops it somewhere else or mouses down on a new item in the list.
Note the important part of this is detecting when and where the user let's go of that mouse, which is the rationale behind handling the DragDrop event of Form1, the parent of listBox1.
Depending on the sophistication and density of the rest of your layout, where you handle DragDrop could be much different for you. This is why it's kind of "hacky", but it's also quite simple. It shouldn't matter, though, where or how many times you null lb_item since it pertains only to that specific ListBox.
I suppose another way to do it would be to track the user's mouse states and act accordingly, which may be more appropriate for you if it's inconceivable to handle a lot of DragDrop stuff.
EDIT: If you wanted to be REAL thorough, you could enumerate through every control of the base form using foreach and programmatically append a handler for the DragDrop event to that control, then remove it when done... but that may be getting a little nutty. I'm sure someone has a better approach.
Related
This is hard to describe in words exactly what the problem is, so I have provided a gif of the issue to hopefully get it across.
link to image:
https://gyazo.com/00ed9cd3e1f2cf377f45e13737fcfc8e
If I disable the scrolling of the frame this does not happen, however this ListView needs to scroll for the entire program to function well.
I have thought about writing my own ListView like system using System.Drawing with panels but I decided to come here first to find a solution that might be less time consuming.
Is there any way I can stop the size from changing when items are moved against the boundaries of the form?
EDIT:
Here's the code for dragging an item:
private void listView1_ItemDrag(object sender, ItemDragEventArgs e)
{
ListViewItem item = e.Item as ListViewItem;
itemStartPos = item.Position;
mouseStartPos = Control.MousePosition;
this.DoDragDrop(item, DragDropEffects.Move);
}
private void listView1_ItemDrag(object sender, ItemDragEventArgs e)
{
ListViewItem item = e.Item as ListViewItem;
itemStartPos = item.Position;
mouseStartPos = Control.MousePosition;
this.DoDragDrop(item, DragDropEffects.Move);
}
private void listView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetData(typeof(ListViewItem)) != null)
{
e.Effect = DragDropEffects.Move;
}
}
private void listView1_DragOver(object sender, DragEventArgs e)
{
ListViewItem item = e.Data.GetData(typeof(ListViewItem)) as ListViewItem;
if(item != null)
{
Point mousePos = Control.MousePosition;
Point itemPos = new Point(itemStartPos.X + mousePos.X - mouseStartPos.X, itemStartPos.Y + mousePos.Y - mouseStartPos.Y);
item.Position = itemPos;
}
}
private void listView1_DragDrop(object sender, DragEventArgs e)
{
//Save pos to registry for loading it back in
}
(hope this formats properly)
The user should be able to freely drag any item within the listview and drop it at the location the release the item at.
I've found an interesting fix to this issue that may only pertain to my specific program but I'll give this question closure.
When the ItemDrag event is called I set the scrollable propety to false, and when DragDrop is called it is set back to true. The reason I say this may only pertain to my program specifically is because when a user drags something the positions are saved to the registry and then subsequently reloaded after the scrollable property is set to true. You see, when the scrollable property is changed it redraws the entire listview so I had to do a bit of a funky work around.
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.
I have a tree view on the left side. Selecting a node displays relevant information in a form on the right side.
Would I be able to keep the tree and any one control (textbox, combobox, checkbox) on the right in focus at the same time? This will enable a user to select a field, make a change, select another node, and without having to go back and select the same field again, just type and change the value of the same field.
Thanx.
EDIT
I suppose one could implement such behaviour manually:
private Control __cFocus;
private void {anyControl}_Focus(object sender, EventArgs e)
{
__cFocus = (Control)sender;
}
private void treeView1_AfterSelect(object sender, EventArgs e)
{
__cFocus.Focus();
}
I was just wondering if there exists an automatic / more elegant solution
EDIT 2
Ok, so it seems I'll have to implement it manually. Manual implementation it is then. However, now there seem to be another problem; not sure if I should ask this as a separate question.
When selecting a node the textbox gains focus as intended, but only when using the keyboard. It doesn't work when selecting a node with the mouse. First I thought that it might be a mouse event that's interfering, but stepping revealed that the MouseUp event fired first and then the AfterSelect event which sets the focus, so I don't think it's interfering. The textbox's Enter event is also fired, but for some reason it loses focus again to the tree.
Thanx
no, you cannot keep two controls in focus at the same time. But what you can do is set the focus to the target control in the treeview AfterSelect event
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
textBox1.Focus();
textBox1.SelectAll();
}
then in your textbox leave, save the changes, like so:
private void textBox1_Leave(object sender, EventArgs e)
{
//save changes here
}
this way, everytime you select an item in the treeview, check your textbox for change and save as needed, then you will refocus on the textbox for your next edit
There only can be one element having the focus!
But I have an idea for you that might solve your problem. Assuming you have a window with a TreeView and a TextBox. Set the HideSelection property of the TreeView to false and subscribe the AfterSelect event (like edeperson already answered) like this:
private void OnTreeViewAfterSelect(object sender, TreeViewEventArgs e)
{
textBox1.Text = e.Node.Text;
textBox1.Focus();
}
Then subscribe the KeyDown event of the TextBox and do following in the event method:
private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == Keys.Up) || (e.KeyCode == Keys.Down))
{
treeView1.Focus();
SendKeys.Send(e.KeyCode == Keys.Up ? "{UP}" : "{DOWN}");
}
}
At last subscribe the Leave event of the TextBox and do following in the event method:
private void OnTextBoxLeave(object sender, EventArgs e)
{
if (treeView1.SelectedNode != null)
{
treeView1.SelectedNode.Text = textBox1.Text;
}
}
And, voilá it should work like you expected it...
If you want to focus on it , you can use usercontrol. you can put your textbox on usercontrol and set focus of this textbox on usercontrol using set properties on treeview select.
No you may not, only one control may be in focus at any given time.
See Moonlight's comment for one way to achieve the behavior that you seek.
I am new to using Event Handling in C# .NET, and I am trying to understand the behavior behind some simple code that I am experimenting with. I am working with a more complicated example, but I am hoping I will get a more focused answer if I simplify the example.
I have the following code which defines a main window with a ListBox that is initialized with values, and a panel in the window. I am working with dragging the ListBox Items and dropping them in the panel. To signify that the panel is reading the DragDrop event, I am simply just changing the background color.
My problem is, it is not changing the background color when I drop the values, hence, the DragDrop is not working. I know this is a bit exaggerated, but I am trying to understand why its not working.
Here is the following code that I am using.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//Allow Panel to accept dropped values
this.panel1.AllowDrop = true;
//Initialize ListBox with sample values
listBox1.Items.Add("First Name");
listBox1.Items.Add("Last Name");
listBox1.Items.Add("Phone");
//Setup DragDrop Event Handler - is this correct, or even needed?
this.panel1.DragDrop += new DragEventHandler(panel1_DragDrop);
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
ListBox box = (ListBox)sender;
String selectedValue = box.Text;
DoDragDrop(selectedValue.ToString(), DragDropEffects.Copy);
}
private void panel1_DragDrop(object sender, DragEventArgs e)
{
//Change Background color to signify value has been dropped
((Panel)sender).BackColor = Color.Black;
}
}
I realize this is an oversimplified example. If you see what I am doing wrong, then please let me know.
EDIT To give an example of why I am confused, I change this example around to put the dragged ListBox Item text into a Textbox when the DragOver event was fired, and it worked fine, but when I tried to do the same thing when they dropped the values over the textbox, I could not get it to work.
Handle the panel's DragEnter event and set e.Effects to something other than None.
I have a question.
I put a treeview control on my form and added some nodes.
public Form1()
{
InitializeComponent();
treeView1.Nodes.Add("root node #1");
treeView1.Nodes.Add("root node #2");
treeView1.Nodes.Add("root node #3");
treeView1.Nodes.Add("root node #4");
treeView1.Nodes.Add("root node #5");
}
I want to change standard right-click behavior. When I right-clicked on a tree Node then Treeview changed for a while selectedIndex. I don't want it. How can I fix standard behavior ?
In ideal, it would be: right click on a treenode text --> context menu appears, right click anywhere outside treenode text --> (absolutely) nothing happens.
private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
// put your logic here like
// ContextMenu1.Show();
}
}
Alex,
Try this. The BeforeSelect handlers event args Cancel is tied to the fact that the right mouse is down. This suppresses the firing of the SelectedIndex changed. The MouseDown tracks if the right mouse is depressed and displays the context menu. The display is safe to move to MouseUp instead of MouseDown. The MouseUp clears the flag indicating that the RightMouse button is depressed.
All of this information on how I did this is available on MSDN. The trick is actually reading the names of all of the events -- Yes I know there are a lot -- then making a list of the "interesting ones" in your case you named SelectedIndex changing, and Mouse clicks. That immediately limits the event names you should read in detail on... If you want the text to not highlight while you're right clicking... well that's a different matter entirely and I caution you against it as it's valuable user feedback.
bool isRBut = false;
private void treeView1_MouseDown(object sender, MouseEventArgs e)
{
isRBut = e.Button == MouseButtons.Right;
if (isRBut)
{
TreeViewHitTestInfo hti =treeView1.HitTest(e.Location);
if (hti.Location == TreeViewHitTestLocations.Label)
contextMenuStrip1.Show(treeView1, new Point(hti.Node.Bounds.Left, hti.Node.Bounds.Bottom));
}
}
private void treeView1_MouseUp(object sender, MouseEventArgs e)
{
isRBut = e.Button == MouseButtons.Right;
}
private void treeView1_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
e.Cancel = isRBut;
}
Additionally, here's a bit of human language trivia for you. Hopefully this will help you in the future. Phrases such as "No, no, no" are interpreted by native English speakers as very rude. Just do your best to list the behavior you see, and the behavior you want. Even if people misunderstand stick to the facts only and leave out the obvious signs of frustration. This will help you get what you're after. As well on SO if someone has a habit of not accepting answers many members here will have a habit of not providing future answers to such members.
Override the MouseClick event, and in the event check if the click was right click
private void treeView1_MouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
//Do something
}
}
You need to handle the NodeMouseClick event and check the right mouse button was clicked:
treeView1.NodeMouseClick += (o, e) => {
if(e.Button == MouseButtons.Right)
{
//show menu...
}
};