As the title suggests I am trying to pass mouse events from one control to another. I'm still a bit new to c# and not even sure if it's possible. I am creating a basic card game, and when I click the mouse down over the deck, I want to pop a card out and drag it without lifting the mouse button. Below is the code for the deck which is on my Main Form
public partial class Form1 : Form
{
int CardWidth = 63;
int CardHeight = 88;
public List<Card> cards = new List<Card>();
public List<Card> deck = new List<Card>();
public Form1()
{
InitializeComponent();
loadCards();
}
private void loadCards()
{
for(int i = 0; i < 10; i++)
{
cards.Add(new Card(i*CardWidth, CardHeight * 0, CardWidth, CardHeight,this));
}
deck.AddRange(cards);
updateDeck();
}
public void updateDeck()
{
Console.WriteLine($"Deck has {deck.Count} cards in it!");
int min = new[] { 4, deck.Count}.Min();
if(deck.Count == 1)
{
pbDeck.Image = Properties.Resources.CardBackDefault;
}
if (deck.Count > 1)
{
switch (min)
{
case 2:
pbDeck.Image = Properties.Resources.Deck2;
break;
case 3:
pbDeck.Image = Properties.Resources.Deck3;
break;
case 4:
pbDeck.Image = Properties.Resources.Deck4;
break;
}
}
if (deck.Count <= 0)
{
pbDeck.Image = Properties.Resources.DeckEmpty;
}
}
private void pbDeck_MouseDown(object sender, MouseEventArgs e)
{
//Pickup Top card if exists
if (deck.Count > 0)
{
//Get last value in Deck List (a.k.a. Last card placed in Deck
Card card = deck.Last();
//Set the card's location to the deck's location
card.Location = pbDeck.Location;
//Load the Card
card.loadCard();
//Remove the card from the deck
deck.Remove(card);
//Update the deck image
updateDeck();
card.card_MouseDown(sender, e);
return;
}
}
}
This is the Card Class
//TODO: May need to be revisited how this value is accessed and modified
public Point Location { get; set; }
//Our Card's visual canvas
public PictureBox card;
//Reference to our main Form
private Form1 Form;
//How we know if the card has been picked up
public bool isMoving = false;
public Card(int X, int Y, int w, int h, Form1 form)
{
Location = new Point(X, Y);
Form = form;
card = new PictureBox();
card.Image = Properties.Resources.CardDefaultTemplate;
card.Location = Location;
card.Name = "card";
card.Size = new Size(w, h);
card.SizeMode = PictureBoxSizeMode.StretchImage;
card.TabIndex = 0;
card.TabStop = false;
card.MouseDown += new MouseEventHandler(card_MouseDown);
card.MouseMove += new MouseEventHandler(card_MouseMove);
card.MouseUp += new MouseEventHandler(card_MouseUp);
card.Paint += new PaintEventHandler(card_Paint);
}
public void loadCard()
{
//Set the location of the card just in case it's changed
card.Location = Location;
//Add this object to the Form
Form.Controls.Add(card);
//Bring it in front of all other items on the form
card.BringToFront();
}
public void removeCard()
{
//Remove card from form
Form.Controls.Remove(card);
}
public void card_MouseDown(object sender, MouseEventArgs e)
{
//Let everyone know we're moving
isMoving = true;
//Bring card to frint of screen for visibility
card.BringToFront();
}
public void card_MouseMove(object sender, MouseEventArgs e)
{
//If we're moving (a.k.a. Mouse is is down on the card)
if (isMoving)
{
//Move the card center to mouse location
card.Location = Form.PointToClient(new Point(Cursor.Position.X - (card.Width / 2), Cursor.Position.Y - (card.Height / 2)));
}
}
private void card_MouseUp(object sender, MouseEventArgs e)
{
//Save the initial locations of the card
int setX = card.Location.X;
int setY = card.Location.Y;
//Let everyone know we're not moving anymore
isMoving = false;
//If the card is past the right boundry
if(setX+card.Width > Form.Width)
{
//Move it back within boundry
setX = Form.Width - card.Width;
}
//If the card is past the left boundry
else if (setX < 0)
{
//Move it back within boundry
setX = 0;
}
//If the card is past the lower boundry
if (setY+card.Height > Form.Height)
{
//Move it back within boundry
setY = Form.Height-card.Height;
}
//If the card is past the upper boundry
else if (setY < 0)
{
//Move it back within boundry
setY = 0;
}
//Set final location
card.Location = new Point(setX, setY);
//If the final location is over the deck image on the form
if (overDeck())
{
//Add this card to the deck
Form.deck.Add(this);
//Update the deck image
Form.updateDeck();
//Remove the card from the form
removeCard();
}
}
private bool overDeck()
{
//If the card's center X value is within the left and right boundries of the deck image on the form
if (card.Location.X + (card.Width / 2) > Form.pbDeck.Location.X && card.Location.X + (card.Width / 2) < Form.pbDeck.Location.X + Form.pbDeck.Width)
{
//If the cards center Y value is within the left and right boundries of the deck image on the form
if (card.Location.Y + (card.Height / 2) > Form.pbDeck.Location.Y && card.Location.Y + (card.Height / 2) < Form.pbDeck.Location.Y + Form.pbDeck.Height)
{
return true;
}
}
//If the above is not true
return false;
}
The cards drag as I intended when out of the deck, but not when I pop them from the deck. Currently they will follow the cursor if I lift off the mouse button, and will drop when clicked again, which is not the desired result
Edit: Added more code for clarity. Changed Title to a more specific Title
Edit 2: Added missing variables from Card Class
I'm not going to try to figure out how you should incorporate this into your code, but the property you are seeking to transfer the mouse input to another control is the Control.Capture Property.
This is boolean that you can set to true for the card PictureBox that you want to respond to moving the mouse. You can set this in another control's MouseDown event handler.
To demonstrate, create a new WinForm project and add a Label and PictureBox control to the form. Wire-up the following event handlers and run the program. Click down on the label and drag the mouse. The PictureBox will follow the cursor.
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
pictureBox1.Location = this.PointToClient(pictureBox1.PointToScreen(e.Location));
}
private void label1_MouseDown(object sender, MouseEventArgs e)
{
pictureBox1.Capture = true;
}
Related
I'm creating a program that has a predefined amount of pictureboxes inserted by the user and then added in a List. the pictureboxs have to grow and each time they would hit eachother or hit the panel boundrys it would have been game over. I can't detect the collisions between them. Problem you can find in the section with 2 foreach. Thanks in advance
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Boxen
{
public partial class FrmBoxes : Form
{
public FrmBoxes()
{
InitializeComponent();
}
//Defined Global Variables for the User input, points, the biggest box, total of points and the average of points
int UserInput;
int Points = 0, BiggestBox = 0, PointsTotal =0;
double AveragePoints = 0;
//List of points of each box and another one to insert the boxes
List<int> PointsList = new List<int>();
List<PictureBox> picboxsList = new List<PictureBox>();
//Random generator for color in the boxes
Random rnd = new Random();
/// <summary>
/// FrmBoxes_Load means when the formular will load, than Info will be shown and Timer will be set to false
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FrmBoxes_Load(object sender, EventArgs e)
{
// Info will be displayed
LblInfo.Text = "Click Box, as bigger as more points you get";
//Timer set to false
TmrFirst.Enabled = false;
}
/// <summary>
/// TmrFirst_Tick, each time the Timer will tick will write on 3 labels different values according with the total points, average points and size of box es
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TmrFirst_Tick(object sender, EventArgs e)
{
//this tree labels will be written from their variables values and the value that its in that variable will be connverted to string
LblPoints.Text = Convert.ToString(PointsTotal);
LblClickPoints.Text = Convert.ToString(AveragePoints);
LblBiggestBox.Text = Convert.ToString(BiggestBox);
//Loop that counts betwwen 0 and the amount of Boxes inside of the List
for (int i = 0; i < picboxsList.Count; i++)
{
//The index of the List will gain a new size of 2 pixels in height and width at each tick
picboxsList[i].Size = new Size(picboxsList[i].Width + 2, picboxsList[i].Height + 2);
}
//Verificacion if the amount of boxes in the List is inferior to the number inputed bby the user
if (picboxsList.Count < UserInput)
{
//Object Box is created with 20 Pixels height and widht and a random color is generated for their background
PictureBox picBox = new PictureBox();
picBox.Size = new Size(20, 20);
picBox.BackColor = Color.FromArgb(rnd.Next(10, 245), rnd.Next(10, 245), rnd.Next(10, 245));
//Event handler will be added to click the box event
picBox.Click += new EventHandler(PicBox_Click);
//Box will be added to the game field with 30 Pixels distance to the edges
PnlGameField.Controls.Add(picBox);
picBox.Location = new Point(rnd.Next(0, PnlGameField.Width - 30), rnd.Next(0, PnlGameField.Height - 30));
//Each created box will be added to the List
picboxsList.Add(picBox);
foreach (Control x in this.Controls)
{
foreach (Control y in this.Controls)
{
if (x != y)
{
if (x.Bounds.IntersectsWith(y.Bounds)) //&& (picBox.Bounds.IntersectsWith(x.Bounds)PnlGameField.Height)) && (picBox.Bounds.IntersectsWith(x.Bounds)PnlGameField.Width))
{
LblInfo.Text = "Game Over";
TmrFirst.Stop();
}
}
}
}
}
//Check that the list is not nothing and contains more than 1 element before continuing.
//for (outerIndex as Int32 = 0; picboxsList.Count - 2)
//for (innerIndex as Int32 = outerIndex + 1; picboxsList.Count - 1)
//if (picboxsList(outerIndex).Bounds.IntersectsWith(picboxsList(innerIndex).Bounds))
//Then slimeList(outerIndex) and slimeList(innerIndex) have collided.
}
/// <summary>
/// Pic_Box, event handler created at click, will add some values and remove after click
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PicBox_Click(object sender, EventArgs e)
{
//event handler called with click interaccion
PictureBox picBox = (PictureBox)sender;
//remove the box from the game field
picBox.Dispose();
//remove the box from the List
picboxsList.Remove(picBox);
//Width will be used to calculate the total points and add each time that is clicked//
PointsTotal += picBox.Width;
//Width will be used to calculate the points per click
Points = picBox.Width;
//Points will be added to the List
PointsList.Add(Points);
//Max value from a box will be token out from the list Points and saved in the variable BiggestBox
BiggestBox = PointsList.Max();
//AveragePoints variable will hold the double value of all the boxes points saved in the list and will update upon click in each box
AveragePoints = Convert.ToDouble(PointsList.Average());
}
/// <summary>
/// When start button will be clicked it creates the amount of boxes defined by the user
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnStart_Click(object sender, EventArgs e)
{
//new text inserted in the label LblInfo to be displayed upon click on the button start
LblInfo.Text = "When the Boxes hit eachother or the edges it's Game Over";
//User input converted to integer and displayed in text box
UserInput = Convert.ToInt32(TxtNumberOfBoxes.Text);
//Timer will be set to true
TmrFirst.Enabled = true;
//Loop to check the user input and add boxes till his given number
for (int i = 0; i < UserInput; i++)
{
//Object Box is created with 20 Pixels height and widht and a random color is generated for their background
PictureBox picBox = new PictureBox();
picBox.Size = new Size(20, 20);
picBox.BackColor = Color.FromArgb(rnd.Next(10, 245), rnd.Next(10, 245), rnd.Next(10, 245));
//Event handler will be added to click the box event
picBox.Click += new EventHandler(PicBox_Click);
//Box will be added to the game field with 30 Pixels distance to the edges
PnlGameField.Controls.Add(picBox);
picBox.Location = new Point(rnd.Next(0, PnlGameField.Width - 30), rnd.Next(0, PnlGameField.Height - 30));
//Each created box will be added to the List
picboxsList.Add(picBox);
}
}
}
}
Answer
Your collision detection does not work because you are checking the controls of the form, not of the PnlGameField. Use the following game loop instead:
private void timer1_Tick(object sender, EventArgs e)
{
var boxes = PnlGameField.Controls.OfType<PictureBox>().ToArray();
foreach (var box in boxes)
{
box.Size = new Size(box.Size.Width + 2, box.Size.Height + 2);
box.Location = new Point(box.Location.X - 1, box.Location.Y - 1);
}
if (CheckCollisions(boxes))
{
EndGame();
}
PnlGameField.Invalidate();
}
private bool CheckCollisions(PictureBox[] boxes)
{
for (int i = 0; i < boxes.Length; i++)
{
var box = boxes[i];
if (box.Left < 0 || box.Right >= PnlGameField.Width
|| box.Top < 0 || box.Bottom >= PnlGameField.Height)
{
box.BorderStyle = BorderStyle.FixedSingle;
return true;
}
for (int j = i+1; j < boxes.Length; j++)
{
var other = boxes[j];
if (box.Bounds.IntersectsWith(other.Bounds))
{
box.BorderStyle = BorderStyle.FixedSingle;
other.BorderStyle = BorderStyle.FixedSingle;
return true;
}
}
}
return false;
}
which I tested and it works 🗸.
Other interesting points.
Scoring
Create a class to keep track of scoring statistics. Convert the points into float since double is too precise (you get averages of 29.9999999997 instead of 30.0) and your conversion to double after averaging is a bug.
public class GameScore
{
readonly List<float> pointsList;
public GameScore()
{
pointsList = new List<float>();
}
public int BoxCount { get => pointsList.Count; }
public float Average { get => pointsList.Count >0 ? pointsList.Average() : 0; }
public float Maximum { get => pointsList.Count>0 ? pointsList.Max() : 0; }
public float Total { get => pointsList.Count>0 ? pointsList.Sum() : 0; }
public void Add(int points)
{
pointsList.Add(points);
}
public void Reset()
{
pointsList.Clear();
}
public override string ToString()
{
return $"Total={Total}, Ave={Average}, Max={Maximum}";
}
}
And the GameScore can be used each time a box is clicked with score.Add(box.Width). See source code below for more details
Box Creation/Removal
One other point to make is you only need to add a new box after one is clicked and removed, so there is no need to check if the current count == user input, and there is no need to duplicate code for adding boxes. Put in a function and call it as needed.
This a general observation that you will be better served if you split your code into functional units (add functions) and call them from the UI handlers. There is no need to keep a separate list of picture boxes, and you can know if the game is running or not by using the Timer.Enabled property.
Source Listing
Below is the full code listing I used for testing, and it largely based on your code, but with things re-arranged around. My hope is that you can get inspired to understand how to be better at structuring your code.
public partial class Form1 : Form
{
static Random rnd = new Random();
readonly GameScore score = new GameScore();
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
timer1.Enabled = false;
}
private void toolStripButton1_Click(object sender, EventArgs e)
{
if (timer1.Enabled)
{
EndGame();
}
else
{
StartGame();
}
}
private void StartGame()
{
toolStripButton1.Text = "Stop";
toolStripButton1.BackColor = Color.FromArgb(255, 128, 128);
if (int.TryParse(toolStripTextBox1.Text, out int count) && count > 0)
{
PnlGameField.Controls.Clear();
for (int i = 0; i < count; i++)
{
AddRandomBox();
}
score.Reset();
toolStripTextBox2.Text = score.ToString();
timer1.Enabled = true;
}
}
private void EndGame()
{
toolStripButton1.Text = "Start";
toolStripButton1.BackColor = Color.FromArgb(128, 255, 128);
timer1.Enabled = false;
}
private void AddRandomBox()
{
//Object Box is created with 20 Pixels height and width and a random color is generated for their background
PictureBox picBox = new PictureBox();
picBox.Size = new Size(20, 20);
picBox.BackColor = Color.FromArgb(rnd.Next(10, 245), rnd.Next(10, 245), rnd.Next(10, 245));
//Event handler will be added to click the box event
picBox.Click += PicBox_Click;
//Box will be added to the game field with 30 Pixels distance to the edges
PnlGameField.Controls.Add(picBox);
picBox.Location = new Point(rnd.Next(0, PnlGameField.Width - 30), rnd.Next(0, PnlGameField.Height - 30));
}
private void PicBox_Click(object sender, EventArgs e)
{
var target = (PictureBox)sender;
if (timer1.Enabled)
{
RemoveBox(target);
}
}
private void RemoveBox(PictureBox box)
{
score.Add(box.Width);
box.Dispose();
PnlGameField.Controls.Remove(box);
AddRandomBox();
toolStripTextBox2.Text = score.ToString();
PnlGameField.Invalidate();
}
private void timer1_Tick(object sender, EventArgs e)
{
var boxes = PnlGameField.Controls.OfType<PictureBox>().ToArray();
foreach (var box in boxes)
{
box.Size = new Size(box.Size.Width + 2, box.Size.Height + 2);
box.Location = new Point(box.Location.X - 1, box.Location.Y - 1);
}
if (CheckCollisions(boxes))
{
EndGame();
}
PnlGameField.Invalidate();
}
private bool CheckCollisions(PictureBox[] boxes)
{
for (int i = 0; i < boxes.Length; i++)
{
var box = boxes[i];
if (box.Left < 0 || box.Right >= PnlGameField.Width
|| box.Top < 0 || box.Bottom >= PnlGameField.Height)
{
box.BorderStyle = BorderStyle.FixedSingle;
return true;
}
for (int j = i+1; j < boxes.Length; j++)
{
var other = boxes[j];
if (box.Bounds.IntersectsWith(other.Bounds))
{
box.BorderStyle = BorderStyle.FixedSingle;
other.BorderStyle = BorderStyle.FixedSingle;
return true;
}
}
}
return false;
}
}
This is what I have so far:
namespace C2360_Ch06_07_Hmwk1_MoveIt
{
public partial class frmMoveit : Form
{
int X = 0;
int Y = 0;
public frmMoveit()
{
InitializeComponent();
}
private void btnBox_Click(object sender, EventArgs e)
{
int currX = btnBox.Location.X;
int curry = btnBox.Location.Y;
btnBox.Location = new Point(0, 0);
}
private void btnEight_Click(object sender, EventArgs e)
{
Button myButton;
myButton = (Button)sender;
if (myButton == btnL)
{
btnBox.Left = 5;
}
else if(myButton == btnR)
{
btnBox.Right = 5;
}
}
}
}
what I am looking to do is try to set btnBox to go left, right, down, up, up left, up right, down left, down right moving 5 spaces. From what you see, I ran the program and it moves all the way to the left.
In addition, I'm looking to set the position of x and y to 0 as well as set the width and height to 25, default, when typed on the text box. Then resize the btnBox to a new width and height when typed in the text box and clicking the resize button.
Hope I made this clear for anyone that can assist me.
I have questions in my program that are jumbled up words and the user tries to un-jumble them. I want them to do this by them clicking multiple picture which have certain letters on it to form the word but I don't know how to connect the picture I have to a certain letter (e.g. Say I have a picture of the letter "a" how do I connect it so that the program knows when that picture is pressed the letter a is pressed).
This is the coding I have so far (to randomize the question and link the answers to it).
*Also I'm very new to coding so any suggestions please can you show what exactly to add/change to my code.
public partial class Level1 : Form
{
Random rnd = new Random(); //sets the random variable
List<string> strStrings = new List<string>() { "ZUZB", "HXAO", "MXAE", "KYCU", "CWEH", "PHIC", "HOCP", "SXIA", "ISHF", "KOJE" };//displays wprds om a list
Dictionary<string, string> dictStrings = new Dictionary<string, string>() //dictionary containing the word as the key, and the answer as the value in the key/value pair.
{
{ "ZUZB", "BUZZ" },
{ "HXAO", "HOAX" },
{ "MXAE", "EXAM" },
{ "KYCU", "YUCK" },
{ "CWEH", "CHEW" },
{ "PHIC", "CHIP" },
{ "HOCP", "CHOP" },
{ "SXIA", "AXIS" },
{ "ISHF", "FISH" },
{"KOJE", "JOKE" }
};
public Level1()
{
InitializeComponent();
}
int skip; //declares the skip variable
int score; //declares the score variable
int question; //decalres the question variable
private void nextButton_Click(object sender, EventArgs e)
{
if (strStrings.Count > 0)
{
string rndWord = strStrings[rnd.Next(0, strStrings.Count())];
lbljumble.Text = rndWord;
strStrings.Remove(rndWord);
}
else
{
lbljumble.Text = "No more questions!";
}
answerLabel.Text = ""; //randomly displays questions in the label until there are no more questions left to ask
score += 10; //add 10 to score and display in label
lblscore.Text = Convert.ToString(score);
question += 1; //add one to question number and display in label
lblqnum.Text = Convert.ToString(question);
tmrtime.Interval = (tmrtime.Interval) - 100; //amount of time taken after each question
}
private void answerButton_Click(object sender, EventArgs e)
{
string answer = (dictStrings.TryGetValue(lbljumble.Text, out answer)) ? answer : "";
answerLabel.Text = answer; //displays answer in label after the answer button is pressed to display the corresponding answer to the question
}
private void btnskip_Click(object sender, EventArgs e)
{
skip = 1; //skip equals 1
if (skip == 1) //of skip equals one
{
skip--; //take one from skip
lblskip.Text = " remaining: no"; //display that no skips are available
}
else
{
lblskip.Text = "No skips remaining"; //display no skips remaining
}
}
}
}
If I understand your question correctly you would like to know what letter was selected/clicked by the user.
You can dynamically create picture boxes, assign the .Name of the picture box to the value you would like the picture box to have then subscribe to the Click event for each picture box.
In the click event check the name of the sending picture box object sender. (you could alternatively use the pictureBx.Tag if you don't want to use the pictureBx.Name )
Here is an example form.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
lblAnswer.Text = "";
DrawLeters();
}
string checkAnswer = "check";
void DrawLeters()
{
this.SuspendLayout();
string[] alphabet = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z".Split(",");
var pictureLocation = new Point(0, 0);
for (int i = 0; i < alphabet.Length; i++)
{
//Create a picture box
PictureBox pictureBx = new PictureBox();
//set the default location of the box (0,0) in this scenario
pictureBx.Location = pictureLocation;
//make the box 16 by 16
pictureBx.Size = new Size(16, 16);
//set the name of the box to that of the letter it represents
pictureBx.Name = alphabet[i];
//assign a click event to the box
pictureBx.Click += PictureBx_Click;
//now create the image that will fill the box
Image img = new Bitmap(16, 16);
using (Graphics graph = Graphics.FromImage(img))
{
graph.Clear(Color.White);
Brush textBrush = new SolidBrush(Color.Black);
graph.DrawString(pictureBx.Name, this.Font, textBrush, 0, 0);
graph.Save();
}
//assign the image to the box
pictureBx.Image = img;
//add the box to the form
this.Controls.Add(pictureBx);
//change the location for the next box
pictureLocation.X += 17;
if (i % 10 == 0 && i > 0)
{
pictureLocation.Y += 17;
pictureLocation.X = 0;
}
}
this.ResumeLayout(false);
this.PerformLayout();
}
private void PictureBx_Click(object sender, EventArgs e)
{ // assign the clicked value to the answer label
if (sender is PictureBox pbx)
lblAnswer.Text += pbx.Name;
}
private void checkAnswerBtn_Click(object sender, EventArgs e)
{
//check the answer
if (lblAnswer.Text == checkAnswer)
lblFeedback.Text = "Correct!!";
else
lblFeedback.Text = "NO!!";
}
private void clearBtn_Click(object sender, EventArgs e)
{
//clear the answer label
lblAnswer.Text = "";
}
}
and the result is this.
i'm just checking that the answer is check you would use your own logic to determine the correct answer.
You can add some picture-boxes and name them as Letter_a_pb, Letter_b_pb etc., create a Click event, and then create an if statement to check which pictureBox is clicked and if it equal to the letter 'a'.
For instance:
string question = "a";
private void Letter_a_pb_Click(object sender, EventArgs e)
{
if (question == "a")
MessageBox.Show("Excellent!");
else
MessageBox.Show("Try Again!");
}
Let me know if this is what you've searched for.
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.
I'm fairly new to OOP and am not sure how I would go about implementing something in my program. My program is pretty much similar to whack a mole and has an array of picture boxes with an image in and an image of a monster moves randomly between the picture boxes with a time interval applied or will move to a new random picture box whenever the user clicks on the monster in time. I have created an monster and a player sub class to try and add some OOP concepts to the program but am not sure how to implement what I want. Basically I have a label for score on my main form and a score variable in my animal class with a value. I want to be able to add the value of score from the label on my form when the user clicks on the picture box with the mole in and take away the value of score from the label when they don't click on it in time.
Here is my code:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
PictureBox[] boxes;
int initialscore = 0;
int time = 0;
int randPos;
public Form1()
{
InitializeComponent();
}
private void boxes_MouseClick(object sender, MouseEventArgs e)
{
PictureBox pb2 = new PictureBox() { Image = Image.FromFile("sword2.png") };
this.Cursor = new Cursor(((Bitmap)pb2.Image).GetHicon());
for (int x = 0; x < 27; x++)
{
if (sender.Equals(boxes[x]))
{
Image grass = Image.FromFile("swamp.png");
PictureBox temp = (PictureBox)sender;
temp.Image = grass;
}
if (sender.Equals(boxes[x]))
{
PictureBox pb = (PictureBox)sender;
if (pb.Tag == "skeleton.png")
initialscore++;
}
}
label1.Text = " Score: " +initialscore.ToString();
}
public void timer1_Tick(object sender, EventArgs e)
{
boxes[randPos].Image = Image.FromFile("swamp.png");
boxes[randPos].Tag = "swamp.png";
Random r = new Random();
randPos=r.Next(0, 27);
boxes[randPos].Image = Image.FromFile("skeleton.png");
boxes[randPos].Tag = "skeleton.png";
}
private void Form1_Load(object sender, EventArgs e)
{
boxes = new PictureBox[27];
int top = 100;
int left = 100;
for (int x = 0; x < 27; x++)
{
boxes[x] = new PictureBox();
boxes[x].Image = Image.FromFile("swamp.png");
boxes[x].Height = 100;
boxes[x].Width = 100;
if (x % 9 == 0)
{
top += 120;
left = 120;
}
else
left += 120;
boxes[x].Top = top;
boxes[x].Left = (50 + left);
Controls.Add(boxes[x]);
this.boxes[x].MouseClick += new
System.Windows.Forms.MouseEventHandler(this.boxes_MouseClick);
label1.Text = " Score: " + initialscore.ToString();
label2.Text = " Time: " + time.ToString();
}
}
}
namespace WindowsFormsApplication1
{
class Monster
{
protected int score;
public Monster()
{
score = 10;
}
}
}
namespace WindowsFormsApplication1
{
class Player:Monster
{
}
}
Nothing has been added in the player class yet.
What do I need to add or change to be able to get the initial score to change by the value of the score in the monster class when clicking on the moving image?
To unify the updating/incrementing and visualization of the score you should extract that to a method:
public void incrementScore(int increment)
{
initialscore += increment;
label1.Text = " Score: " + initialscore.ToString();
}
in the Form1_Load you call this like:
incrementScore(0);
for the click on the monster you have different possibilities:
if all the monsters have the same points you can make it a static variable in the Monster class.
protected static int Score = 10;
which allows you to use it in the boxes_MouseClick event handler:
incrementScore(Monster.Score);
in case all monsters have another value you have to hold the score variable as an instance variable, identify somehow the instance of the monster class you clicked on and increment with this value