I am creating a Word Add-In and in order to allow dragging something from a custom task pane to the document, I have followed the following guide:
http://msdn.microsoft.com/en-us/library/office/hh780901(v=office.14).aspx
There are some real drawbacks using this approach.
First, the transparent Windows Form (or WPF in my case) that catches the drop event is the size of the Window, not the document, and RangeFromPoint always returns a value, even if we aren't over the document (for instance, if we are over the Ribbon). So once you drag something and this form is created, no matter where you drop, it will be placed in the document. There is no graceful way to cancel once you've started.
My question is:
Has anyone done any work with Drag and Drop in a Word Add In, and found a better way to handle it than the supplied example by Microsoft?
It would be nice to either use the current solution, but know when the user is not dragging over the document or have that transparent window only show over the document area.
Hope you already had your answer.
I got a solution for my own.
So, my requirement:
I have a custom pane, which contains a listbox, each item is a normal string. When I drag an item from the listbox into the document, at a specific location, I want to insert a merge field at that location. The name of the merge field is the text of the item.
It was simple at first, then I got a problem just like you describe in your question.
About the code
So, there is a listbox, you need to handle mouseDown and mouseMove, don't worry about mouseUp.
In mouseDown handler, I record the boundary, if the mouse moves out of that boundary, the drag will start.
Then, in listBox_MouseMoveHandler, I check position of the mouse to start the dragdrop. And I have to use DragDropEffects.Copy for the DoDragDrop method.
DoDragDrop((sender as ListControl).SelectedValue, DragDropEffects.Copy);
With that option, SelectedValue will be inserted at the drop position, and after it is inserted, it will also be selected.
Then, I just check if selection is not empty, and replace the selected text with the merge field. Of course, I collapsed the selection before DoDragDrop. And that is the whole trick.
private int _selectedItemIndex;
private Rectangle dragBoxFromMouseDown;
private void CustomizationForListBox(ListBox listBox)
{
listBox.ItemHeight = 25;
listBox.DrawMode = DrawMode.OwnerDrawFixed;
listBox.DrawItem += ListBox_DrawItem;
listBox.MouseDoubleClick += listBox_MouseDoubleClick;
listBox.MouseMove += listBox_MouseMoveHandler;
listBox.MouseUp += listBox_MouseUp;
listBox.MouseDown += (sender, e) =>
{
// Handle drag/drop
if (e.Button == MouseButtons.Left)
{
_selectedItemIndex = listBox.IndexFromPoint(e.Location);
// 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.
Size dragSize = SystemInformation.DragSize;
// Create a rectangle using the DragSize, with the mouse position being
// at the center of the rectangle.
dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
e.Y - (dragSize.Height / 2)), dragSize);
}
};
}
private void listBox_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// Reset the drag rectangle when the mouse button is raised.
dragBoxFromMouseDown = Rectangle.Empty;
}
}
private void listBox_MouseMoveHandler(object sender, MouseEventArgs e)
{
// Handle drag and drop
// To check if the Mouse left button is clicked
if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
{
// If the mouse moves outside the rectangle, start the drag.
if (dragBoxFromMouseDown != Rectangle.Empty &&
!dragBoxFromMouseDown.Contains(e.X, e.Y))
{
// Collapse current selection, now we know nothing is selected
Globals.ThisAddIn.Application.Selection.Collapse(WdCollapseDirection.wdCollapseEnd);
//Start Drag Drop
DoDragDrop((sender as ListControl).SelectedValue, DragDropEffects.Copy);
if (_selectedItemIndex != -1)
{
// If the drag/drop was successful, there dropped text must be selected
if (!String.IsNullOrWhiteSpace(Globals.ThisAddIn.Application.Selection.Text))
{
// Replace the selected text with a merge field MergeFieldHelper.InsertSingleMergeField(mergeFieldInfos[_selectedItemIndex].Name);
}
}
}
}
}
Related
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.
I have two pictureboxes in my Windows Forms Application. Each of these pictureboxes hold an image. pictureBox1 is small, only 122 x 52, and pictureBox2 is much larger (459 x 566). What I want to do is be able to drag-and-drop the image of picturebox1 onto picturebox2 and a new image will be created and saved. Whereever x&y coordinates I place pictureBox1's image, it will "stamp" it right at that location in pictureBox2. And then pictureBox2's image will be modified and saved. So simply by dragging-and-dropping, the user should be able to "stamp" images onto pictureBox2 easily. Is this possible?
Mr Snrub,
If you use drag drop, you are in control of what you wish to do. Generally, in the MouseDown event of the control you determine whether a dragevent is starting or not. I keep a form property.
private Point _mouseDownPoint;
I set that in the drag from control during MouseDown
protected override void OnMouseDown(MouseEventArgs e)
{
_mouseDownPoint = e.Location;
}
In the OnMouseMove event for that same control. This code ensures that the user is most likely trying to drag and begins the dragdrop. This code comes from a usercontrol, so the this in the DoDragDrop may have to be changed in your case.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button != MouseButtons.Left) return;
var dx = e.X - _mouseDownPoint.X;
var dy = e.Y - _mouseDownPoint.Y;
if (Math.Abs(dx) > SystemInformation.DoubleClickSize.Width || Math.Abs(dy) > SystemInformation.DoubleClickSize.Height)
{
DoDragDrop(this, DragDropEffects.Move);
}
}
Your control(s) which may receive the drop should have their DragEnter events coded. Here we have a DragEnter event that differentiates between a ToolStripButton and a custom UserControl DSDPicBox. Controls without a DragEnter event coded will display the noDrop Icon when dragged over.
private void Control_DragEnter(object sender, DragEventArgs e)
{
var button = e.Data.GetData(typeof(ToolStripButton)) as ToolStripButton;
if (button != null)
{
e.Effect = DragDropEffects.Copy;
return;
}
var element = e.Data.GetData(typeof(DSDPicBox)) as DSDPicBox;
if (element == null) return;
e.Effect = DragDropEffects.Move;
}
And lastly, you must handle the drop. the panelDropPoint is the coordinates of where the item was dropped. You could use that to position your new graphic in the old one. You will have to render the picture at the new resolution if you are changing its size.
private void panel_DragDrop(object sender, DragEventArgs e)
{
// Test to see where we are dropping. Sender will be control on which you dropped
var panelDropPoint = sender.PointToClient(new Point(e.X, e.Y));
var panelItem = sender.GetChildAtPoint(panelDropPoint);
_insertionPoint = panelItem == null ? destination.Controls.Count : destination.Controls.GetChildIndex(panelItem, false);
var whodat = e.Data.GetData(typeof(ToolStripButton)) as ToolStripButton;
if (whodat != null)
{
//Dropping from a primitive button
_dropped = true;
whodat.PerformClick();
return;
}
}
I have removed a few items from the code that would just muddy the waters. This code may not work out of the box, but should get you closer.
Regards,
Marc
I have a ListView with the View set to LargeIcon.
I specifically need to detect when multiple items were selected by dragging a selection box around them with the mouse.
(For example I don't want to know when items were selected by CTRL + Click)
I thought I could simply do it by keeping track of whether the mouse was down while it was moving which would indicate dragging, then on mouse up if it was a drag then I can set another variable to indicate this.
In my example below mouseDown is set to true, but when I keep the mouse down and move it isDrag is never set to true and I can't see what I'm doing wrong.
(Edit: isDrag becomes true if I remove the if clause which is weird because as I said mouseDown is definitely true).
I realise the code is a little longer than it needs to be but it's for clarity.
bool mouseDown;
bool isDrag;
bool wasDrag;
private void listView1_MouseDown(object sender, MouseEventArgs args)
{
wasDrag = false;
mouseDown = true;
}
private void listView1_MouseMove(object sender, MouseEventArgs args)
{
if (mouseDown)
isDrag = true; // <-- Never becomes true, even though mouseDown is true
}
private void listView1_MouseUp(object sender, MouseEventArgs args)
{
if (isDrag)
wasDrag = true;
mouseDown = false;
isDrag = false;
}
I know it'll be something stupid. Please put me out of my misery.
Alternatively if someone knows of a better was to detect a dragging selection (what's the proper term?) then I'm all ears.
Can you try this:
private void listView1_MouseMove(object sender, MouseEventArgs args)
{
isDrag = mouseDown;
}
I think for some reason, your event listView1_MouseUp still fires, which makes your isDrag variable set to other than the intended value. Try to put breakpoints on both MouseMove and MouseUp events to see the sequence with which they are firing.
After further investigation I've discovered that for a ListView control the MouseMove event will not fire while MouseDown is still occurring and fires immediately after releasing the mouse.
I can only assume that the logic built into this control that allows you to select multiple files by dragging a selection is messing with these events and essentially making them synchronous.
I've put together a basic workaround for this. It's not ideal but it does the job so I thought I'd share.
Basically when the mouse goes down I record the position. When the mouse goes up I check to see if it has moved more than a certain distance in any direction. If it has not I consider it a click, if it has I consider it a drag.
// Records the mouse position on mousedown
int beforeMoveX;
int beforeMoveY;
// How far in pixels the mouse must move in any direction
// before we consider this a drag rather than a click
int moveBounds = 20;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
// Save the mouse position
beforeMoveX = e.X;
beforeMoveY= e.Y;
}
private void listView1_MouseUp(object sender, MouseEventArgs e)
{
// Did we move more than the bounds in any direction?
if (e.X < (beforeMoveX - moveBounds) ||
e.X > (beforeMoveX + moveBounds) ||
e.Y < (beforeMoveY - moveBounds) ||
e.Y > (beforeMoveY + moveBounds))
{
// DRAGGED!
}
else
{
// NOT DRAGGED!
}
}
I have a winform on which i want to allow the user to move a control.
The control is (for now) a vertical line : label with border and a width of 1.
The context is not very important but i'll give it to you anyways. I have a background with some graphics and i'd like the user to be able to slide a guideline above the graphics. The graphics are made with the NPlots library. It looks something like this:
http://www.ibme.de/pictures/xtm-window-graphic-ramp-signals.png
If i can find out how the user can click and drag the label/line control around the screen, i can solve my guideline problem. Please help.
The code for this can get a bit complex, but essentially you will need to capture the MouseDown, MouseMove, and MouseUp events on your form. Something like this:
public void Form1_MouseDown(object sender, MouseEventArgs e)
{
if(e.Button != MouseButton.Left)
return;
// Might want to pad these values a bit if the line is only 1px,
// might be hard for the user to hit directly
if(e.Y == myControl.Top)
{
if(e.X >= myControl.Left && e.X <= myControl.Left + myControl.Width)
{
_capturingMoves = true;
return;
}
}
_capturingMoves = false;
}
public void Form1_MouseMove(object sender, MouseEventArgs e)
{
if(!_capturingMoves)
return;
// Calculate the delta's and move the line here
}
public void Form1_MouseUp(object sender, MouseEventArgs e)
{
if(_capturingMoves)
{
_capturingMoves = false;
// Do any final placement
}
}
In WinForms, you can handle the MouseDown, MouseMove and MouseUp events of a control. On MouseDown, set some bit or reference telling your form what control the mouse was clicked on, and capture the X and Y of the mouse from MouseEventArgs. On MouseMove, if a control is set, adjust its X and Y by the difference between your last captured X and Y and the current coordinates. On MouseUp, release the control.
I would set up an "edit mode" for this; when the user enters this mode, the current event handlers of your form's controls should be detached, and the movement handlers attached. If you want to persist or revert these changes (like you're making a custom form designer your client can use to customize window layouts), you'll also need to be able to take some sort of snapshot of the before and after layouts of the controls.
I wrote a component to do exactly that: move a control on a form (or move a borderless form on the screen). You can even use it from the designer, without writing any code.
http://www.thomaslevesque.com/2009/05/06/windows-forms-automatically-drag-and-drop-controls-dragmove/
Here's an extension method that you can use for any control. It uses Rx and is based on the A Brief Introduction to the Reactive Extensions for .NET, Rx post and sample by Wes Dyer.
public static class FormExtensions
{
public static void EnableDragging(this Control c)
{
// Long way, but strongly typed.
var downs =
from down in Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
eh => new MouseEventHandler(eh),
eh => c.MouseDown += eh,
eh => c.MouseDown -= eh)
select new { down.EventArgs.X, down.EventArgs.Y };
// Short way.
var moves = from move in Observable.FromEvent<MouseEventArgs>(c, "MouseMove")
select new { move.EventArgs.X, move.EventArgs.Y };
var ups = Observable.FromEvent<MouseEventArgs>(c, "MouseUp");
var drags = from down in downs
from move in moves.TakeUntil(ups)
select new Point { X = move.X - down.X, Y = move.Y - down.Y };
drags.Subscribe(drag => c.SetBounds(c.Location.X + drag.X, c.Location.Y + drag.Y, 0, 0, BoundsSpecified.Location));
}
}
Usage:
Button button1 = new Button();
button1.EnableDragging();
Well in all honesty there is a simpler way where by you initialize a global boolean variable called whatever you like, in this case, isMouseClicked. On your control you wish to allow dragging you go to its mouse down event,
Make sure these event are your control events not your forms event.
if (e.button == MouseButtons.left)
//this is where you set the boolean to true
Then go to its mouse move event
if (isMouseClicked == true)
//You then set your location of your control. See below:
Button1.Location = new Point(MousePosition.X, MousePosition.Y);
On your mouse up make sure to set your isMouseClicked to false;
I have a panel which holds many pictureboxes. Each picturebox has registered "contextRightMenu" as their context menu.
What i want when the context menu pops up is to get the current mouseposition.
I have tried getting the mouseposition by using mouseDown and click, but these events happens after one of the items of the context menu is clicked, and that is too late.
the popup event of the context menu does not deliver mouse event args, so i don't know how to get the mouseposition.
If i can get mouse event args it is easy.
Then i just can:
this.contextRightClick.Popup += new System.EventHandler(this.contextRightClick_Popup);
// If EventArgs include mouseposition within the sender
private void contextRightClick_Popup)(object sender, EventArgs e)
{
int iLocationX = sender.Location.X;
int iLocationY = sender.Location.Y;
Point pPosition = new Point(iLocationX + e.X, iLocationY + e.Y); // Location + position within the sender = current mouseposition
}
Can anyone help me either get some mouse event args, or suggest a event that will run before the contextmenu pop ups?
Thanks in advance
Do you want the cursor location relative to the PictureBox that was right clicked or relative to the parent Panel, or the parent Window or possibly just the screen position?
The following might help as a starting point. Here I get the current mouse cooridnates on the entire screen then using the SourceControl from the contextRightMenu, which is a reference to the instance of the control that was right clicked on, we convert the screen coordinates to a point relative to the source control.
void contextRightMenu_Popup(object sender, EventArgs e)
{
ContextMenu menu = sender as ContextMenu;
if (menu != null)
{
// Get cursor position in screen coordinates
Point screenPoint = Cursor.Position;
// Convert screen coordinates to a point relative to the control
// that was right clicked, in your case this would be the relavant
// picture box.
Point pictureBoxPoint = menu.SourceControl.PointToClient(screenPoint);
}
}
Handle the MouseClick of your PictureBox. Something like this (in vb.net):
Sub OnMouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) handles YourPictureBox.mouseclick
If e.Button = Windows.Forms.MouseButtons.Right then
'if you need the screen posistion
PointToScreen(New System.Drawing.Point(e.X, e.Y))
'if you need just the location
e.Location
end if
end sub
You can try the MouseClick event of picturebox and get the location if it is a right click.
you may wantto take a look to ContextMenuStrip Class and Control.ContextMenuStripChanged Event, some exemple here