The following code is supposed to move UIElements in a canvas, when the mouse hovers over them, and the user hits Ctrl.
void keydown(Object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
{
control++;
if (control == 1)
{
drag = true;
elem = (UIElement)Mouse.DirectlyOver;
}
else
drag = false;
control %= 2;
}
}
void mousemove(object sender, MouseEventArgs e)
{
Point p = e.GetPosition(canvas);
if (drag)
{
if (elem == null) return;
//Canvas.SetLeft(myButton, p.X); <-- this works, but then why doesn't it work when I generalize it?
Canvas.SetLeft(elem, p.X);
Canvas.SetTop(elem, p.Y);
}
}
Any Shapes component, e.g. Rectangles will move when I hover the mouse over them, and hit control..But it doesn't work with Buttons, TextBoxes, TextViews, etc. Can anyone explain?
The documentation for Mouse.DirectlyOver says:
Controls can be composed of multiple elements. DirectlyOver reports the specific element in the composite control the mouse pointer is over and not the control itself. For example, depending on which part of a Button the pointer is over, the DirectlyOver property could report the TextBox of the Content property or the ButtonChrome.
In other words: A Button is made up of several sub-elements, like a ButtonChrome and a TextBlock (usually not a TextBox -- I think that's a typo on the MSDN page). When you call Mouse.DirectlyOver, you're probably getting one of those elements, rather than the Button.
Since those elements are not parented to a Canvas (they're parented to something in the Button's control template, most likely a Grid), setting the Canvas.Left and Canvas.Top attached properties will have no effect.
You probably want to walk up the visual tree (using VisualTreeHelper.GetParent) until you find something you're interested in dragging. How you determine whether you're interested in any given element is up to you. You could go until you find something that's parented to your Canvas, or you could just go until you find something that's one of a given set of types (stopping when you find something that descends from Control might be a decent place to start).
My guess is that those events are swallowed by the implementation of the controls that don't work.
I'm not in front of my computer now, but isn't there a previewmousemove event? Try using that?
Update: scratch that!
Just use e.Souce. The event source is designed to be the element your likely to be interested in. You'll need to cast to UIElement, but I'm pretty sure that'll work.
Related
Before you jump to conclusions and ask "Why don't you just Hide() the form?" let me explain.
I have the following:
A PDF Viewer control PDFViewer.
A form containing a TextBox TextBoxForm.
A parent form containing PDFViewer and TextBoxForm (with Owner property set to this parent).
TextBoxForm is an overlay to PDFViewer and should be hidden when outside the bounds of PDFViewer, which can happen if the user scrolls. Here's how I achieve that:
PDFViewer_OnScroll(object sender, HandledMouseEventArgs e){
TextBoxForm.SetBounds(GetBounds(PDFViewer));
TextBoxForm.Top = [some value];
}
Ìnside TextBoxForm I do the following:
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
UpdateFormRegion(); // Offset bounds so it's relative to the form itself instead of the screen
Invalidate();
}
This works fine when TextBoxForm is completely or partially inside the bounds of PDFViewer, but as soon as it's moved completely outside, whichever part of TextBoxForm was rendered before will still be rendered (I assumed it's because Windows decides there is no need to re-paint the form, when it's not inside it's own bounds).
It's easy enough to make a check for when this happens, but the reason I don't want to call TextBoxForm.Hide() is because that causes the TextBox to lose focus.
Any way I can get the form hidden without losing focus?
I just thought of a possible solution (not a very elegant one, however):
if (Right < _bounds.Left || Left > _bounds.Right || Bottom < _bounds.Top || Top > _bounds.Bottom)
{
Top = -20000;
Left = -20000;
}
Basically, whenever the form is moved/resized I check if it is now completely outside the supplied bounds and move it way off screen if true.
A less "hacky" solution is still welcome.
I am working with a SplitterPanel in winforms where on the right hand side I want a custom dropdown list control which displays a 2 columns dropdown list.
The problem is that with there being two columns I want to be able to have a larger dropdown list area than the actual dropdown, and therefore overlap the SplitterPanel if the list doesn't fit in the split area.
I have tried using .BringToFront();, however this does not work on the SplitterPanel and the control is hidden. I come from a web background where I would have used z-index for this but I am stumped with winforms. See below image of my issue.
Does anyone know how I can resolve this?
The z-index will determine which child controls sit higher and can overlap which others child controls. But it never helps when you want a (real as opposed to drop downs or menues) child overlap its own container. This never happens; and since the CheckedListBox is the child of the split panel it will never overlap it.
You will need to make the CheckedListBox sit not inside the splitter panel but in its Parent so the they are 'brethren'. Let's assume the SplitContainer sits in a TabPage tabPage8. Then you can show it fully by moving it to that tabPage:
moveCtlToTarget(tabPage8, checkedListBox1);
using this function.
void moveCtlToTarget(Control target, Control ctl)
{
// get screen location of the control
Point pt = ctl.PointToScreen(Point.Empty);
// move to the same location relative to the target
ctl.Location = target.PointToClient(pt);
// add to the new controls collection
ctl.Parent = target;
// move all up
ctl.BringToFront();
}
As I don't know how you create and show it, resetting it is up to you. Note that as it now is no longer in the split panel it will not move when you move the splitter..
You may want to do this only the first time and later align it with the ComboBox..
TaW's answer above helped my solve my issue. I modified it slightly for my situation where I moved the parameters into the method as I already had my checkbox control set as a property of the control and got the target by looping up the parents until I got to the top.
private void moveCtlToTarget()
{
Control Target = Parent;
while (Target.Parent != null)
Target = Target.Parent;
Point pt = CheckBox.PointToScreen(Point.Empty);
CheckBox.Location = Target.PointToClient(pt);
CheckBox.Parent = Target;
CheckBox.BringToFront();
}
I have a user control which consists of two controls and four buttons, arranged on a Form: the two controls are on the sides and the buttons in the middle in a vertical row.
When using the control in an app, I place it on a form.
Now, when re-sizing the form horizontally, the two controls just move left or right w/o changing their size.
What I need is that the controls stay anchored to the middle of the form and grow to the sides (sorry about the lack of clarity, I prepared screenshots but the site wouldn't let me attach them).
Is there a way to accomplish this without overriding the Resize event?
Use a TableLayoutPanel as base for your user control.
You need 3 columns and one row. The middle column needs to have a fixed size, and the other 2 you set to 50%. Not to worry, .Net is smart enough to calculate the percent they actually take.
Inside the right and left columns you put your controls and set the Dock property of both to fill. In the middle column you put a panel and set it's Dock property to fill as wall, and In that panel you put the buttons in the middle.
Set your table layout panel Dock to fill as well, and when adding the user control to the form use Dock top, bottom or fill as well.
Erratum:
The above code works most of the time, but it fails for certain Move-Resize sequences. The solution is to respond to the Move and Resize events of the parent form (the consumer of the control), not of the control itself.
One more thing: due to the event firing order (Move first followed by Resize, had to move the working code from Resize() to Move(), which seems counterintuitive but it seems the right way nevertheless.
It seems indeed that it cannot be done in the Designer, but here is the solution using overrides.
It works ok, except for some control flickering which I haven't been able to overcome.
public partial class SB : UserControl
{
//variables to remember sizes and locations
Size parentSize = new Size(0,0);
Point parentLocation = new Point (0,0);
......
// we care only for horizontal changes by dragging the left border;
// all others take care of themselves by Designer code
public void SB_Resize(object sender, EventArgs e)
{
if (this.Parent == null)
return;//we are still in the load process
// get former values
int fcsw = this.parentSize.Width;//former width
int fclx = this.parentLocation.X;//former location
Control control = (Control)sender;//this is our custom control
// get present values
int csw = control.Parent.Size.Width;//present width
int clx = control.Parent.Location.X;//present location
// both parent width and parent location have changed: it means we
// dragged the left border or one of the left corners
if (csw != fcsw && clx != fclx)
{
int delta = clx - fclx;
int lw = (int)this.tableLayoutPanel1.ColumnStyles[0].Width;
int nlw = lw - delta;
if (nlw > 0)
{
this.tableLayoutPanel1.ColumnStyles[0].Width -= delta;
}
}
this.parentSize = control.Parent.Size;//always update it
this.parentLocation = control.Parent.Location;
}
//contrary to documentation, the Resize event is not raised by moving
//the form, so we have to override the Move event too, to update the
//saved location
private void SB_Move(object sender, EventArgs e)
{
if (this.Parent == null)
return;//we are still in the load process
this.parentSize = this.Parent.Size;//always update it
this.parentLocation = this.Parent.Location;
}
}
The above code works most of the time, but it fails for certain Move-Resize sequences. The solution is to respond to the Move and Resize events of the parent form (the consumer of the control), not of the control itself.
One more thing: due to the event firing order (Move first followed by Resize, had to move the working code from Resize() to Move(), which seems counterintuitive but it seems the right way nevertheless.
I'm following Bea Stollnitz's blog post on implementing drag and drop on an data bound ItemsControl. It works very nicely, but I have a question for anyone who's experienced something similar...
When I begin dragging the item, there is a small, dashed rectangle at the bottom of the mouse. I cannot figure out at all how to hide that rectangle. Does anyone know how to get rid of this? I would add a screenshot, but when I do a Print Screen, the rectangle doesn't appear.
I think it has something to do with a focus setting on the AdornerLayer that the "DraggedAdorner" is added to.
Thanks!
Try this in the Style of the Visual that is surrounded by the rectangle:
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
EDIT: The effect that you are seeing is a result of the DragDropEffects.Move assignment. You can mitigate this visual by simply changing the following line (Line #168 in the sample):
DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move);
To this:
DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.None);
Thus setting the DragDropEffect to DragDropEffects.None
NOTE: In the sample, it evaluates the DragDropEffects value in the process of performing the drag & drop, so you would need to work around this (Probably a simple AttachedProperty, or even casting the Sender as a FrameworkElement and using the Tag property), but this should resolve the visual issue.
I hope this helps, and if I can help you further feel free to let me know. Good luck!
The dashed rectangle is actually part of the default cursor used when performing a drag and drop "Move" operation.
This is the default "Move" cursor:
And this is the default "Copy" cursor (if you hold CTRL while dragging):
You can get more control over what mouse cursor is shown by overriding UIElement.OnGiveFeedback, or subscribing to the UIElement.GiveFeedback Routed Event.
When handling the event and changing the cursor, make sure to set e.Handled = true; to prevent the cursor from flickering.
For example, use this override on the element initiating the Drag operation (see this walkthrough for more info):
protected override void OnGiveFeedback(GiveFeedbackEventArgs e)
{
if (e.Effects.HasFlag(DragDropEffects.Copy))
{
Mouse.SetCursor(Cursors.Cross);
}
else if (e.Effects.HasFlag(DragDropEffects.Move))
{
Mouse.SetCursor(Cursors.Pen);
}
else
{
Mouse.SetCursor(Cursors.No);
}
e.Handled = true;
}
I've been coding a Windows app chess game in C# as an exercise in honing my skills, and also because it's fun. I have included functionality that allows a player to select the option to highlight the squares a piece can legally move to when it gets clicked. A CustomControl handles the rendering of the chessboard and it also highlights the squares.
It all works as planned until the player begins to drag the piece to a new square. The moment the mouse moves, the highlights go away. I suspect that a Paint event is raised and the board redraws itself. And since the highlights are not part of the initial board layout, they don't get drawn.
What I would like to happen is for the squares to remain highlighted until the piece is dropped on its destination square. Is it possible to accomplish this? Any suggestions will be appreciated.
Psuedo code:
void piece_MouseDown(object sender, MouseEventArgs e)
{
Piece piece = (Piece)sender;
legalSquares = CalculateLegalSquares(piece.CurrentSquare);
if (legalSquares.Count > 0 && this.showLegalMoves)
{
chessBoard1.HighlightSquares(legalSquares);
}
// I believe a Paint event gets raised either here...
piece.DoDragDrop(piece, DragDropEffects.Move);
}
void piece_DragEnter(object sender, DragEventArgs e)
{
// ...or here, that removes the highlights.
if (e.Data.GetDataPresent("Chess.Piece"))
{
e.Effect = DragDropEffects.Move;
}
else
{
e.Effect = DragDropEffects.None;
}
}
void piece_DragDrop(object sender, DragEventArgs e)
{
Piece piece = (Piece)e.Data.GetData("Chess.Piece");
if (piece.CurrentSquare != dropSquare)
{
if (legalSquares.Contains(dropSquare))
{
// This is where I’d like the highlights to stop
// DoStuff()
}
}
}
It sounds like you are highlighting the valid squares by drawing directly, but this will get erased on any repaint. You will probably lose the highlights if your window is repainted for other reasons also, such as minimizing and restoring it, or dragging another window on top of it.
If this is the case, you probably need to override the OnPaint method and do your highlighting there. When you want to change what is highlighted, set some state in your class to control what is drawn as highlighted in the OnPaint method, and then Invalidate your window.