For each control loop only affecting one Control - c#

I will try to keep this as specific as possible.
This is my code:
foreach (Control c in this.Controls)
{
if (c.Height > 25)
{
c.BackColor = Color.Red;
pictureBox1.Location = new Point(x1, y1);
if (pictureBox1.Bounds.IntersectsWith(c.Bounds))
{
isCollide = true;
label1.Text = "true";
c.BackColor = Color.Green;
}
else
{
isCollide = false;
label1.Text = "false";
}
}
}
My goal is to loop through all of the controls on my form, and detect all of which have more than 25 pixels in height, which determines whether or not they are a wall in my game.
The "isCollide" variable which is being set to "true" simply makes it so that I cannot run through it.
The problem is that it only affects 1 picture box (wall). However, I have chosen to change the colour of all the selected controls to green as a check to see which controls are affected by the loop, and yet this affects all walls as it should..
My question is, why does the color affect all the controls that my code looped through, but the collision detection doesn't work on them all? Is it because the "isCollide" variable is set to "false" and "true" at the same time due to colliding with one wall and simultaneously NOT colliding with another?
How may I remedy this problem?
This is the code which uses "isCollide":
public void move(string Direction)
{
if (Direction == "Up")
{
//b = x / a = y
reset();
x1 = x;
y1 = y - 5;
isCollided();
if (isCollide == false)
{
y -= 5;
}
}
*Note: Not the end of the code, similar code has been applied to all directions (top, left, right, bottom)

The final value of isCollide will be based on the collision of the last control that is iterated through in the foreach loop. If you don't care which object the collision occurs with, I recommend breaking out of the loop once a collision is found.
Something like this.
foreach (Control c in this.Controls)
{
if (c.Height > 25)
{
c.BackColor = Color.Red;
pictureBox1.Location = new Point(x1, y1);
if (pictureBox1.Bounds.IntersectsWith(c.Bounds))
{
isCollide = true;
label1.Text = "true";
c.BackColor = Color.Green;
break;
}
else
{
isCollide = false;
label1.Text = "false";
}
}
}

Its because isCollide is just a local variable and not a property of your control like your c.BackColor is.
You need to define isCollide on your Control class to retain the state of isCollide for each control object in your Controls list:
public class Control
{
public bool isCollide {get; set;}
public string BackColor {get; set;}
public int Height {get; set;}
}
foreach (Control c in this.Controls)
{
if (c.Height > 25)
{
c.BackColor = Color.Red;
pictureBox1.Location = new Point(x1, y1);
if (pictureBox1.Bounds.IntersectsWith(c.Bounds))
{
c.isCollide = true;
label1.Text = "true";
c.BackColor = Color.Green;
}
else
{
c.isCollide = false;
label1.Text = "false";
}
}
}

A different reason could be that the controls on your form are nested into containers (e.g. GroupBox).
In this case you would have to recursively check the elements in their Control property as well.

Related

Detecting collision between two rectangles on a WPF Canvas

I am extremely new to programming, and I have started off with C#. I am now trying to make my first game, I decided on snake. So far I have been trying to research this question, but all of the answers I see are ones that pertain to people who are using a different method of moving their snake around.
My program uses two doubles (left and top) in order to store where the snake is on the Canvas. My program also uses two doubles for the "food" in the game, called randomFoodSpawnLeft and randomFoodSpawnTop.
My question is this. How does one detect collision between two rectangular objects with just a left and top value? I am rather confused.
snakeWindow is the Window, snakeHead is the rectangle that represents a snake, left is the left value of the snake, top is the top value of the snake.
void timer_Tick(object sender, EventArgs e)
{
double left = Canvas.GetLeft(snakeHead);
double top = Canvas.GetTop(snakeHead);
if (keyUp)
{
top -= 3;
}
else if (keyDown)
{
top += 3;
}
else if (keyLeft)
{
left -= 3;
}
else if (keyRight)
{
left += 3;
}
// These statements see if you have hit the border of the window, default is 1024x765
if (left < 0)
{
left = 0;
gameOver = true;
}
if (top < 0)
{
top = 0;
gameOver = true;
}
if (left > snakeWindow.Width)
{
left = 0;
gameOver = true;
}
if (top > snakeWindow.Height)
{
top = 0;
gameOver = true;
}
// Statements that detect hit collision between the snakeHead and food
//
if (foodEaten == true)
{
spawnFood();
textBlockCurrentScore.Text += 1;
}
// If gameOver were to be true, then the game would have to end. In order to accomplish this I display to the user that the game is over
// and the snakeHead is disabled, and should restart.
if (gameOver == true)
{
keyRight = false;
keyLeft = false;
keyUp = false;
keyDown = false;
top = 0;
left = 0;
textBlockGameOver.Text = "GAME OVER!";
snakeCanvas.Background = Brushes.Blue;
}
Canvas.SetLeft(snakeHead, left);
Canvas.SetTop(snakeHead, top);
}
You can use System.Windows.Rect.IntersectsWith. Try it like this:
Rect rect1 = new Rect(left1, top1, widht1, height1);
Rect rect2 = new Rect(left2, top2, widht2, height2);
bool intersects = rect1.IntersectsWith(rect2);
Of course you will have to check the snake's head against all it's parts.

All control property change in loop

I'm writing a code to apply transparent background to all of my LinkLabel controls. I don't wanna do it with hardcoded label id and repeating the same code.
foreach (Control cType in this.Controls)
{
if (cType.GetType() == typeof(LinkLabel))
{
LinkLabel linkTemp = cType as LinkLabel;
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}
}
When I'm trying the above piece of code it make odd numbered buttons to be transparent i.e.
Button 1 - transparent
Button 2 - NOT
Button 3 - transparent
But while I'm repeating the same code applied on different linklabel ids manually its working fine. What I'm missing here?
This is an inevitable side-effect of you changing the Parent property of the control. That modifies the Controls collection since the LinkLabel is moved from the form's Controls collection to the pictureBox1.Controls collection. Modifying a collection while iterating it normally generates an InvalidOperationException but this was not done for the Controls collection.
You need to solve this by iterating the collection backwards. Like this:
for (int ix = this.Controls.Count-1; ix >= 0; --ix) {
var linkTemp = this.Controls[ix] as LinkLabel;
if (linkTemp == null) continue;
// etc..
}
Some programmers like using Linq, that's okay too but obfuscates the issue a bit and isn't nearly as efficient. Key is to make a copy that cannot be affected by modifying the collection:
foreach (var linkTemp in this.Controls.OfType<LinkLabel>().ToList()) {
// etc..
}
You can use this 'odd' construction:
int i = 1;
foreach (LinkLabel item in this.Controls.OfType<LinkLabel>())
{
if ((i = 1 - i) == 0)
{
LinkLabel linkTemp = cType as LinkLabel;
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}
}
Some tips for code formatting
Change
if (cType.GetType() == typeof(LinkLabel))
to
if (cType is LinkLabel)
Change
foreach (Control cType in this.Controls)
{
if (cType.GetType() == typeof(LinkLabel))
{
LinkLabel linkTemp = cType as LinkLabel;
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}
}
to
foreach (LinkLabel linkTemp in this.Controls.OfType<LinkLabel>())
{
Point pos = this.PointToScreen(linkTemp.Location);
pos = pictureBox1.PointToClient(pos);
linkTemp.Parent = pictureBox1;
linkTemp.Location = pos;
linkTemp.BackColor = Color.Transparent;
}

Prevent ToolStripMenuItems from jumping to second screen

I have an application that is mostly operated through NotifyIcon's ContextMenuStrip
There are multiple levels of ToolStripMenuItems and the user can go through them.
The problem is, that when the user has two screen, the MenuItems jump to second screen when no space is available. like so:
How can I force them to stay on the same screen? I've tried to search through the web but couldn't find an appropriate answer.
Here is a sample piece of code i'm using to test this senario:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var resources = new ComponentResourceManager(typeof(Form1));
var notifyIcon1 = new NotifyIcon(components);
var contextMenuStrip1 = new ContextMenuStrip(components);
var level1ToolStripMenuItem = new ToolStripMenuItem("level 1 drop down");
var level2ToolStripMenuItem = new ToolStripMenuItem("level 2 drop down");
var level3ToolStripMenuItem = new ToolStripMenuItem("level 3 drop down");
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
notifyIcon1.Icon = ((Icon)(resources.GetObject("notifyIcon1.Icon")));
notifyIcon1.Visible = true;
level2ToolStripMenuItem.DropDownItems.Add(level3ToolStripMenuItem);
level1ToolStripMenuItem.DropDownItems.Add(level2ToolStripMenuItem);
contextMenuStrip1.Items.Add(level1ToolStripMenuItem);
}
}
It is not easy, but you can write code in the DropDownOpening event to look at where the menu is at (its bounds), the current screen, and then set the DropDownDirection of the ToolStripMenuItem:
private void submenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
if (menuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
// Current bounds of the current monitor
Rectangle Bounds = menuItem.GetCurrentParent().Bounds;
Screen CurrentScreen = Screen.FromPoint(Bounds.Location);
// Look how big our children are:
int MaxWidth = 0;
foreach (ToolStripMenuItem subitem in menuItem.DropDownItems)
{
MaxWidth = Math.Max(subitem.Width, MaxWidth);
}
MaxWidth += 10; // Add a little wiggle room
int FarRight = Bounds.Right + MaxWidth;
int CurrentMonitorRight = CurrentScreen.Bounds.Right;
if (FarRight > CurrentMonitorRight)
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
Also, make sure you have the DropDownOpening event hooked up (you would really need to add this to every menu item):
level1ToolStripMenuItem += submenu_DropDownOpening;
I have solved it this way:
For the ContextMenuStrip itself to open on a desired screen, I created a ContextMenuStripEx with the following methods:
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
Rectangle dropDownBounds = new Rectangle(x, y, width, height);
dropDownBounds = ConstrainToBounds(Screen.FromPoint(dropDownBounds.Location).Bounds, dropDownBounds);
base.SetBoundsCore(dropDownBounds.X, dropDownBounds.Y, dropDownBounds.Width, dropDownBounds.Height, specified);
}
internal static Rectangle ConstrainToBounds(Rectangle constrainingBounds, Rectangle bounds)
{
if (!constrainingBounds.Contains(bounds))
{
bounds.Size = new Size(Math.Min(constrainingBounds.Width - 2, bounds.Width), Math.Min(constrainingBounds.Height - 2, bounds.Height));
if (bounds.Right > constrainingBounds.Right)
{
bounds.X = constrainingBounds.Right - bounds.Width;
}
else if (bounds.Left < constrainingBounds.Left)
{
bounds.X = constrainingBounds.Left;
}
if (bounds.Bottom > constrainingBounds.Bottom)
{
bounds.Y = constrainingBounds.Bottom - 1 - bounds.Height;
}
else if (bounds.Top < constrainingBounds.Top)
{
bounds.Y = constrainingBounds.Top;
}
}
return bounds;
}
(ConstrainToBounds method is taken from the base class ToolStripDropDown via Reflector)
for the nested MenuItems to open on the same screen as ContextMenuStrip, I created a ToolStripMenuItemEx (which derives from ToolStripMenuItem). In my case it looks like this:
private ToolStripDropDownDirection? originalToolStripDropDownDirection;
protected override void OnDropDownShow(EventArgs e)
{
base.OnDropDownShow(e);
if (!Screen.FromControl(this.Owner).Equals(Screen.FromPoint(this.DropDownLocation)))
{
if (!originalToolStripDropDownDirection.HasValue)
originalToolStripDropDownDirection = this.DropDownDirection;
this.DropDownDirection = originalToolStripDropDownDirection.Value == ToolStripDropDownDirection.Left ? ToolStripDropDownDirection.Right : ToolStripDropDownDirection.Left;
}
}
The code of #David does not fix if the menu is opened in the left side of second screen. I have improved that code to work on all screen corner.
private void subMenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem mnuItem = sender as ToolStripMenuItem;
if (mnuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
//get position of current menu item
var pos = new Point(mnuItem.GetCurrentParent().Left, mnuItem.GetCurrentParent().Top);
// Current bounds of the current monitor
Rectangle bounds = Screen.GetWorkingArea(pos);
Screen currentScreen = Screen.FromPoint(pos);
// Find the width of sub-menu
int maxWidth = 0;
foreach (var subItem in mnuItem.DropDownItems)
{
if (subItem.GetType() == typeof(ToolStripMenuItem))
{
var mnu = (ToolStripMenuItem) subItem;
maxWidth = Math.Max(mnu.Width, maxWidth);
}
}
maxWidth += 10; // Add a little wiggle room
int farRight = pos.X + mnuMain.Width + maxWidth;
int farLeft = pos.X - maxWidth;
//get left and right distance to compare
int leftGap = farLeft - currentScreen.Bounds.Left;
int rightGap = currentScreen.Bounds.Right - farRight;
if (leftGap >= rightGap)
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
I did not try the solution by tombam. But since the others didn't seem to work, I came up with this simple solution:
private void MenuDropDownOpening(object sender, EventArgs e)
{
var menuItem = sender as ToolStripDropDownButton;
if (menuItem == null || menuItem.HasDropDownItems == false)
return; // not a drop down item
// Current bounds of the current monitor
var upperRightCornerOfMenuInScreenCoordinates = menuItem.GetCurrentParent().PointToScreen(new Point(menuItem.Bounds.Right, menuItem.Bounds.Top));
var currentScreen = Screen.FromPoint(upperRightCornerOfMenuInScreenCoordinates);
// Get width of widest child item (skip separators!)
var maxWidth = menuItem.DropDownItems.OfType<ToolStripMenuItem>().Select(m => m.Width).Max();
var farRight = upperRightCornerOfMenuInScreenCoordinates.X + maxWidth;
var currentMonitorRight = currentScreen.Bounds.Right;
menuItem.DropDownDirection = farRight > currentMonitorRight ? ToolStripDropDownDirection.Left :
ToolStripDropDownDirection.Right;
}
Note that in my world, I was not concerned about multiple levels of cascading menus (as in the OP), so I did not test my solution in that scenario. But this works correctly for a single ToolStripDropDownButton on a ToolStrip.

problems updating picture box control

I have two controls on the same form. Both controls contain an ObjectListView control. The one listview was created with the graphical editor in visual studio. This one is not causing any issues. The other listview is created programmatically at run-time. I have defined an event handler for each control that gets called when the hot item changes and they are both firing when they should. Both event handlers call the same code to update a picturebox control. The problem is that the picturebox does not get updated when the programmatically defined listview is asking it to. I am positive the event handler is getting called because my code writes to a text file as well as updating the picture box. The text file gets updated but the picture box does not. I have tried updating, invalidating, and refreshing the PicutureBox as well as the parent form, but I just can not get it to update.
I am not sure if this is an ObjectListView issue or a standard WinForms problem. I realize my question is very vague but I am not sure how to clarify it without posting all my code. Any advice would be appreciated.
Here is the code that the event handler calls:
public void ShowBitmap(object sender, HotItemChangedEventArgs e, ObjectListView lv, string type)
{
ObjectListView olv = sender as ObjectListView;
if (sender == null)
{
return;
}
switch (e.HotCellHitLocation)
{
case HitTestLocation.Nothing:
break;
case HitTestLocation.Group:
break;
case HitTestLocation.GroupExpander:
break;
default:
if (e.HotColumnIndex == 0)
{
pictureBox1.Hide();
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
int rowIndex = e.HotRowIndex;
string text = "";
if (type == "Main Parts")
{
TypedObjectListView<MainRadanProjectPartsPart> tlist = new TypedObjectListView<MainRadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Parts")
{
TypedObjectListView<RadanProjectPartsPart> tlist = new TypedObjectListView<RadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Nests")
{
TypedObjectListView<MainRadanProjectNestsNest> tlist = new TypedObjectListView<MainRadanProjectNestsNest>(lv);
text = tlist.Objects[rowIndex].FileName;
}
if (text != null)
{
Point screenCoords = Cursor.Position;
Point controlRelatedCoords = lv.PointToClient(screenCoords);
if (controlRelatedCoords.Y < oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
int yPos = controlRelatedCoords.Y + 60;
pictureBox1.Location = new Point(xPos, yPos);
}
else if (controlRelatedCoords.Y > oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
//int yPos = controlRelatedCoords.Y - pictureBox1.Height;
int yPos = controlRelatedCoords.Y - pictureBox1.Height + 30;
pictureBox1.Location = new Point(xPos, yPos);
}
pictureBox1.Show();
pictureBox1.BringToFront();
olvTreeViewMainParts.Focus();
lv.Focus();
pictureBox1.Visible = true;
DrawSymbol(text);
oldCursorPosition = controlRelatedCoords; // save the cursor position to track cursor direction between calls
}
else
{
DrawSymbol("");
}
}
else
{
pictureBox1.Hide();
}
break;
}
}
Here is the event handler for the programmaticaly defined listview:
// track the cursor as it moves over the items in the listview
private void olvPartsListView_HotItemChanged(object sender, HotItemChangedEventArgs e)
{
ShowBitmap(sender, e, olvPartsListView, "Parts");
}

Removing Elements Programmatically in Silverlight

I am starting to learn silverlight and to practice I am doing a simple space invaders type videogame.
My issue is I am creating custom controls (bullets) programmatically like so:
if(shooting)
{
if(currBulletRate == bulletRate)
{
Bullet aBullet = new Bullet();
aBullet.X = mouse.X - 5;
aBullet.Y = mouse.Y - Ship.Height;
aBullet.Width = 10;
aBullet.Height = 40;
aBullet.Tag = "Bullet";
LayoutRoot.Children.Add(aBullet);
currBulletRate = 0;
}
else
currBulletRate++;
}
However I am having trouble removing them once they go off bounds (leave the LayoutRoot).
I tried looping throught the LayoutRoot.Children and removing but I can't seem to get it right.
UIElement[] tmp = new UIElement[LayoutRoot.Children.Count];
LayoutRoot.Children.CopyTo(tmp, 0);
foreach (UIElement aElement in tmp)
{
Shape aShape = aElement as Shape;
if (aShape != null && aShape.Tag != null)
{
if (aShape.Tag.ToString().Contains("Bullet"))
{
if (Canvas.GetTop(aShape) + aShape.ActualHeight < 0) // This checks if it leaves the top
{
LayoutRoot.Children.Remove(aElement);
}
else if(Canvas.GetTop(aShape) > Canvas.ActualHeight) // This condition checks if it leaves the bottom
{
LayoutRoot.Children.Remove(aElement);
}
}
}
}
The code you pasted was only checking if the bullet left the top of the canvas.

Categories