Moving multiple controls in Winforms runtime - c#

I'm building a runtime designer for Winforms. The goal of this designer is to move controls in their container (e.g. a Form or a Panel).
When I move one control at a time everything work perfect. When I select multiple controls, things go wrong, but not always:
When I select two Buttons, the move works OK, but when I select a Label and a TextBox, the label moves in the wrong direction.
Here is an example what happens: https://www.screencast.com/t/sPaH4VNr
Here's my code:
protected virtual void ControlMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
IsMouseDown = true;
Point startPosition = Control.PointToScreen(new Point(e.X, e.Y));
OffsetMove = new Point();
OffsetMove.X = Control.Location.X - startPosition.X;
OffsetMove.Y = Control.Location.Y - startPosition.Y;
}
else
{
IsMouseDown = false;
}
}
protected virtual void ControlMouseMove(object sender, MouseEventArgs e)
{
this.Control.Cursor = IsMovable && IsMoveAllowed(e) ? Cursors.SizeAll : Cursors.Default;
if (IsMouseDown && this.Control.Cursor == Cursors.SizeAll)
{
foreach (var control in Parent.SelectedControls.Select(sc => sc.Control))
{
Point newPoint = control.PointToScreen(e.Location);
newPoint.Offset(OffsetMove);
if (control.Location != newPoint)
{
control.Location = newPoint;
control.Parent.Invalidate();
}
}
}
}
I hope someone sees what I am doing wrong.
Thanks in advance.

Related

How to reset the location of multiple buttons in a form when one button is moved

I'm making a drag and drop game for my A level computing coursework. My drag and drop works fine, but I have 6 buttons/options and I want to reset the locations on the other 5 buttons when I move on the 1 button. The 6 buttons are named btnAnswer1, btnAnswer2, btnAnswer3, etc.
I already tried to search for a solution, and it still doesn't work
bool isDragged = false;
Point ptOffset;
private void buttonMouseDown(object sender, MouseEventArgs e) {
Button theButton = (Button)sender;
if (e.Button == MouseButtons.Left) {
isDragged = true;
Point ptStartPosition = theButton.PointToScreen(new Point(e.X, e.Y));
ptOffset = new Point();
ptOffset.X = theButton.Location.X - ptStartPosition.X;
ptOffset.Y = theButton.Location.Y - ptStartPosition.Y;
} else {
isDragged = false;
}
}
private void buttonMouseMove(object sender, MouseEventArgs e) {
Button theButton = (Button)sender;
if (isDragged) {
Point newPoint = theButton.PointToScreen(new Point(e.X, e.Y));
newPoint.Offset(ptOffset);
theButton.Location = newPoint;
}
}
private void buttonMouseUp(object sender, MouseEventArgs e) {
Button theButton = (Button)sender;
isDragged = false;
if ((theButton.Location.X >= 190 && theButton.Location.X <= 468) && (theButton.Location.Y >= 42 && theButton.Location.Y <= 236)) {
answerText = theButton.Text;
if (answerText == RandomQuestion[0].CorrectAnswerPosition) {
MessageBox.Show("Correct Answer");
}
else MessageBox.Show("Wrong Answer");
// disableDragDrop();
}
}
I don't know how to reset the locations of the other 5 buttons when I move 1 button.
You can change the button off postion this way
yourButton.Location = new Point(50, 50); //x and y cordinate off position
You should rest your 5 buttons positions in the method where you move 1 button with my method. Hope this helps.

Dragging Objects in C#

I am attempting to drag Music Notes vertically, up and down a Music Staff. However, rather than a constant drag, I would like the music notes to only be allowed to be dragged onto particular intervals (only specific y-coordinates). For example, in a vertical line, a music note can be dragged on to coordinates (0,0), (0,5) or (0,10).
Below is my relevant code:
private Point MouseDownLocation;
private void Note_MouseDown(object sender, MouseEventArgs e)
{
foreach (MusicNote mn in panel2.Controls.OfType<MusicNote>())
{
if (sender == mn)
{
if (e.Button == MouseButtons.Left)
{
MouseDownLocation = e.Location;
}
}
}
}
private void Note_MouseMove(object sender, MouseEventArgs e)
{
foreach(MusicNote mn in panel2.Controls.OfType<MusicNote>())
{
if (sender == mn)
{
if (e.Button == MouseButtons.Left)
{
mn.Top = e.Y + mn.Top - MouseDownLocation.Y;
}
}
}
}
Any help is appreciated. Thank you!
Basically, you need to check if you drag up or drag down
You should want to check the MouseDown.X and compare it to the MouseUp.X (or Y if you want to check vertical direction as well). It is important to note that (0, 0) is the upper left of your screen. So you need to compare the X position from mouse down event to the mouse up event.
here's an example with one label that moves up and down in steps of 10
private void label1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (label1.Location.Y > 0 && label1.Location.Y < panel1.Size.Height) // not the most accurate way, but you get the idea
{
mPointDown = new Point(e.X, e.Y);
}
}
}
private void label1_MouseUp(object sender, MouseEventArgs e)
{
bool movedUp, movedDown;
if (e.Y == mPointDown.Y)
{
movedUp = movedDown = false;
}
else
{
movedUp = e.Y < mPointDown.Y;
movedDown = !movedUp;
}
if (movedDown)
{
label1.Location = new Point(label1.Location.X, label1.Location.Y + 10);
}
else if (movedUp)
{
label1.Location = new Point(label1.Location.X, label1.Location.Y - 10);
}
}
private void label1_MouseMove(object sender, MouseEventArgs e)
{
mouseDownPoint = e.Location;
}

Use Fillpath with mouse input as flood fill between drawn paths?

(New to this and playing around with a basic paint application) Ive found detailed instructions to code a flood-fill but as i am new it is very hard to understand every bits of it, and instead of copying, i would like to try to make my own simple(small scale) flood-fill.
Would it be possible to use fillpath as a flood-fill? i would draw paths and use my mouse to determine my x,y, on screen and have the graphicspath find out if it has borders(points from the drawn paths) and if so, fill these paths with a color?
this is what ive come up with but obviously it doesnt work, so how would i go about to make this working?
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
Graphics g;
readonly Pen pen = new Pen(Color.Navy, 2);
Point oldCoords;
GraphicsPath graphicsPaths = new GraphicsPath();
bool spaceFound = false;
public Form1()
{
InitializeComponent();
g = panel1.CreateGraphics();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
Point mousePt = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Right &&
graphicsPaths.IsVisible(mousePt))
{
spaceFound = true;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (oldCoords.IsEmpty)
graphicsPaths.StartFigure();
else
{
graphicsPaths.AddLine(oldCoords, new Point(e.X, e.Y));
g.DrawPath(pen, graphicsPaths);
}
oldCoords = new Point(e.X, e.Y);
}
else
oldCoords = Point.Empty;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
g.DrawPath(pen, graphicsPaths);
if(spaceFound == true)
{
g.FillPath(Brushes.AliceBlue, graphicsPaths);
}
}
}
}
Yes, this is quite possible; of course you would want to store the path in a List<GraphicsPath> in the MouseUp event to allow for more filled shapes..
You need to correct a few issues in your code:
Set the path.FillMode to Winding
Never cache a Graphics object
Never use control.CreateGraphics()
Don't cache Pens or Brushes
Only draw in the Paint event, unless you do not want the drawing to persist
The last point might actually apply here: Maybe you don't want the currently drawing outline to stay visible? In that, and only that case you can stick with drawing it in the MouseMove with a Graphics object created there on the fly.
Here is a corrected version:
Point oldCoords;
GraphicsPath graphicsPaths = new GraphicsPath() { FillMode = FillMode.Winding };
bool spaceFound = false;
private void drawPanel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && graphicsPaths.IsVisible(e.Location))
{
spaceFound = true;
drawPanel1.Invalidate();
}
}
private void drawPanel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (oldCoords.IsEmpty) graphicsPaths.StartFigure();
else
{
graphicsPaths.AddLine(oldCoords, new Point(e.X, e.Y));
drawPanel1.Invalidate();
}
oldCoords = new Point(e.X, e.Y);
}
else oldCoords = Point.Empty;
}
private void drawPanel1_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Black, 2f))
e.Graphics.DrawPath(pen, graphicsPaths);
if (spaceFound == true)
{
e.Graphics.FillPath(Brushes.AliceBlue, graphicsPaths);
}
}
Note that it will fill your path but not in the way of a true floodfill, i.e. it will always fill the whole path, not just the innermost segment you have clicked in. For a true floodfill much more involved code is needed that actually goes over all neighbouring pixels starting at the click location..
Examples of a true floodfill are here and here

How to draw a selectable line?

I want to create an application which the user is able to manipulate the line he draw. Something like Deleting the line or Selecting it. How should I do that?
Thanks in advance
I managed to do it using a hard coded rectangle. But I don't still have an idea how to do it using the drawLine() Can I use drawPath to do the hit test?
Here is the code:
private bool selectGraph = false;
private Rectangle myrec = new Rectangle(50, 50, 100, 100);
private Graphics g;
private void panel1_Paint(object sender, PaintEventArgs e)
{
SolidBrush sb = new SolidBrush(Color.Blue);
Pen p = new Pen(Color.Blue, 5);
e.Graphics.DrawRectangle(p, myrec);
e.Graphics.FillRectangle(sb, myrec);
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
Point mPT = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Left)
{
if (myrec.Contains(mPT))
{
selectGraph = true;
button1.Enabled = true;
}
else
{
selectGraph = false;
button1.Enabled = false;
}
}
Invalidate();
}
Well you could start with something like a simple Line class:
public class Line
{
public Point Start { get; set; }
public Point End { get; set; }
}
Then you could have your form:
private Line Line = new Line();
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Red, this.Line.Start, this.Line.End);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
this.Line.Start = e.Location;
this.Refresh();
}
else if (e.Button == MouseButtons.Right)
{
this.Line.End = e.Location;
this.Refresh();
}
}
So basically then they could delete the this.Line maybe on "MiddleButton" click or something. This should be enough to get you started.
I've created a sample that shows how this can be done. Set some break points and see how things are done.
There is no easy one line solution for this. You would have to program this yourself.
You have to keep track of every object you have drawn. In the onmousedown event you have to find out if the mouse has clicked on or near an object you want to move/delete by comparing coordinates. Then you need to draw some visual guide that the line is 'selected'. Deleting is now quite easy by removing the object from the collection.
For drag and drop you have to do something similar by changing the coordinates of the object according to the mouse move.

Close tab on winforms tab control with middle mouse button

Is there any easy (5 lines of code) way to do this?
The shortest code to delete the tab the middle mouse button was clicked on is by using LINQ.
Make sure the event is wired up
this.tabControl1.MouseClick += tabControl1_MouseClick;
And for the handler itself
private void tabControl1_MouseClick(object sender, MouseEventArgs e)
{
var tabControl = sender as TabControl;
var tabs = tabControl.TabPages;
if (e.Button == MouseButtons.Middle)
{
tabs.Remove(tabs.Cast<TabPage>()
.Where((t, i) => tabControl.GetTabRect(i).Contains(e.Location))
.First());
}
}
And if you are striving for least amount of lines, here it is in one line
tabControl1.MouseClick += delegate(object sender, MouseEventArgs e) { var tabControl = sender as TabControl; var tabs = tabControl.TabPages; if (e.Button == MouseButtons.Middle) { tabs.Remove(tabs.Cast<TabPage>().Where((t, i) => tabControl.GetTabRect(i).Contains(e.Location)).First()); } };
Solution without LINQ not so compact and beautiful, but also actual:
private void TabControlMainMouseDown(object sender, MouseEventArgs e)
{
var tabControl = sender as TabControl;
TabPage tabPageCurrent = null;
if (e.Button == MouseButtons.Middle)
{
for (var i = 0; i < tabControl.TabCount; i++)
{
if (!tabControl.GetTabRect(i).Contains(e.Location))
continue;
tabPageCurrent = tabControl.TabPages[i];
break;
}
if (tabPageCurrent != null)
tabControl.TabPages.Remove(tabPageCurrent);
}
}
Don't have enough points to post a comment to the provided solutions but they all suffer from the same flaw: The controls within the removed tab are not released.
Regards
You could do this:
private void tabControl1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Middle)
{
// choose tabpage to delete like below
tabControl1.TabPages.Remove(tabControl1.TabPages[0]);
}
}
Basically you are just catching a mouse click on tab control and only deleting a page if the middle button was clicked.

Categories

Resources