Could anyone explain why I get a NullReferenceException, when I create a new Button and try to reference it? Creating the Button and assigning the Name works fine, but referencing it does not.
using System;
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 DragNDrop_1
{
public partial class Form1 : Form
{
//Variables----------------------------------------------------------------------
int ButtonID = 100;
bool isDraggingButton = false;
public Form1()
{
InitializeComponent();
}
//----------------------------------------------------------------------Variables
private void btn_addButton_Click(object sender, EventArgs e)
{
AddButton();
}
public void AddButton()
{
Button b = new Button();
b.Name = "Button" + ButtonID.ToString();
b.Text = "Button" + ButtonID.ToString();
b.Location = new Point(ButtonID, ButtonID);
ButtonID = ButtonID + 100;
pnl_DragNDrop.Controls.Add(b);
isDraggingButton = true;
}
private void DragTimer_Tick(object sender, EventArgs e)
{
if (isDraggingButton == true)
{
Point mouse = PointToClient(MousePosition);
this.Controls["Button" + ButtonID.ToString()].Location = new Point(mouse.X + 20, mouse.Y + 20);
}
}
}
}
The Exception occurs in the timer, where I try to reference the last button created. I read trough some threads regarding this Exception, but I still can't spot the error.
Yes, I know that this is very messy and I should propably create a custom Loop or de-/re- activate the timer, but this is just for testing purposes. Note that I'm new to C# and Windows Forms.
EDIT: As explained by Lukasz M, this is a Problem regarding Ownership (maybe the Term is not correct, it's the best german-english Translation I can come up with). This is neither the Focus of the Question from the Thread I "duplicated", nor is it mentioned in the Answer. If it is though, I have to question my English-skills. Anyway, I just wanted to make clear, that I indeed read the Thread, but wasn't able to spot a Solution. Maybe it's just the lack of English- and C#-Skills, but I'm pretty sure that this is not a duplicate.
It's because in AddButton method You create the button, but do not add it directly to the form's controls, but to the pnl_DragNDrop.Controls collection.
You can try to change this:
this.Controls["Button" + ButtonID.ToString()].Location = new Point(mouse.X + 20, mouse.Y + 20);
to this:
pnl_DragNDrop.Controls["Button" + ButtonID.ToString()].Location = new Point(mouse.X + 20, mouse.Y + 20);
and it should work fine.
Another way to do it would be saving the b button in a class field instead of a variable inside the method. This way, You could refer to the control in a different method without the need to find it by Id in the Controls collection. You may also want to add more than one button with different Id values, so the exact implementation for storing and refering to buttons created then, may depend on actual use case.
Update
To make the code actually work, please also notice that after You create the b control, You modify the variable used to compose its name:
ButtonID = ButtonID + 100;
Then, in DragTimer_Tick method You use the modified value to rebuild the control's name, but it's already different, so the control is not found.
When searching the control by name, You can either save the previous value of ButtonID or save the whole string used as button's name (as mentioned in the comments) to be able to use it to find the control later.
Related
Good afternoon,
I am new to object-oriented programming, .NET and C#. I am studying these previous mentioned topics and presently am doing a relatively simple programming assignment which turned out not to be so simple after all, at least ... for me still.
I want to create a Windows Form Application which contains one form that is filled with country flags (.png, 128px x 128px). In the Form_OnLoad() the files are read and the flags are stored array of PictureBox objects and set several object attributes. Then the form is filled neatly in rows of 8 flags. So far so good.
Problem:
I would like to add a MouseOver event-handler attached to each PictureBox that generates a ToolTip with the country name of the specific flags. In the event-handler I don't know what code to put after what I have managed to do myself already. Specifically, I would like to address the array with flags from the MouseOver event-handler method, but it's not visible from there. I am just stuck here, although my intuition tells me that I am not far from my goal, at this moment my mind decided to give up on me a few meters away from the finish line. Would someone be so kind to help me out with this please?
Here's what I got already:
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
namespace WorldFlags
{
public partial class FormWorldFlags :Form {
public FormWorldFlags() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
int col = 0, row = 0;
string imageDirectory = #"D:\Documents\Visual Studio 2017\Projects\ITvitae\WorldFlags\flags\";
string[] imageFileList = Directory.GetFiles(imageDirectory);
PictureBox[] countryFlag = new PictureBox[imageFileList.Length];
for (int i = 0; i < imageFileList.Length; i++) {
countryFlag[i] = new PictureBox();
countryFlag[i].Name = Path.GetFileNameWithoutExtension(imageFileList[i]);
countryFlag[i].Image = Image.FromFile(imageFileList[i]);
countryFlag[i].Location = new Point(col * 128 + 1, row * 128 + 1);
countryFlag[i].Size = new Size(128, 128);
countryFlag[i].MouseHover += FormWorldFlags_MouseHover;
if (col + 1 == 8) {
row++;
col = 0;
} else
col++;
Controls.Add(countryFlag[i]);
}
}
private void FormWorldFlags_MouseHover(object sender, EventArgs e) {
ToolTip countryName = new ToolTip();
countryName.SetToolTip(?????)
}
}
};
Thanks so much in advance.
Joeri van der Heijden
change your handler to the following:
private void FormWorldFlags_MouseHover(object sender, EventArgs e) {
ToolTip countryName = new ToolTip();
countryName.SetToolTip(sender, "country name");
}
If you want to access the array within the handler you can can just move it out of the form load function. Alternatively you can make use of the Tag property when loading the images and access the value in the handler.
Ok, so as I go through learning C# I have run into an issue that I can't quite wrap my mind around.
I am building an idle game to learn more than books have taught me. Anyways, I am adding an "Auto-clicker" function. I thought that I should add a timer that would count to 1 second and add gold to the player's score. Maybe this is not the best way to approach this, but here is what I have so far:
Updated Code as per requested with Errors trying to load System.Timers.Timer;
using System;
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;
using System.Timers;
namespace IdleClicker1
{
public partial class Form1 : Form
{
public double gold = 0;
public double goldPerClick = 1;
public double upgradeCost = 20;
public double autoMinerLevel = 0;
public double autoMinerCost = 10;
public System.Timers.Timer autoMineTimer = new System.Timers.Timer();
public Form1()
{
InitializeComponent();
}
private void btnGetGold_Click(object sender, EventArgs e)
{
gold += goldPerClick;
updateGoldShown();
}
public void updateGoldShown()
{
lblGold.Text = "Gold: " + gold.ToString();
lblGoldPerClick.Text = "Gold per click: " + goldPerClick.ToString();
lblAutoMiner.Text = "Auto-Miner Level: " + autoMinerLevel.ToString();
}
private void btnUpgradeClick_Click(object sender, EventArgs e)
{
if (gold >= upgradeCost)
{
gold = gold - upgradeCost;
goldPerClick = goldPerClick + 1;
upgradeCost = upgradeCost + 10;
lblUpgradeCost.Text = "Cost to upgrade: " + upgradeCost.ToString();
updateGoldShown();
}
else
{
MessageBox.Show("Sorry bub... not enough gold yet!", "Error buddy!");
}
}
public void btnAutoMiner_Click(object sender, EventArgs e)
{
if (gold >= autoMinerCost)
{
autoMinerLevel++;
gold = gold - autoMinerCost;
autoMinerCost = autoMinerCost + 10;
btnAutoMiner.Text = "Buy Auto-Miner for: " + autoMinerCost.ToString();
updateGoldShown();
//Adding a new timer
System.Timers.Timer autoMineTimer = new System.Timers.Timer();
autoMineTimer.Tick += new EventHandler(timer_Tick);
autoMineTimer.Interval = 1000;
autoMineTimer.Enabled = true;
autoMineTimer.Start();
}
else {
MessageBox.Show("Sorry... not enough gold!", "Error again... yo!");
}
}
void timer_Tick(object sender, EventArgs e)
{
updateGoldShown();
btnGetGold.PerformClick();
}
}
}
Basically, this is a very simple setup. I am just trying to learn by practice and applying myself. So when the user clicks on btnAutoMiner, it should start a timer that would add whatever the goldPerClick to the player's gold. I don't want an exact answer (otherwise I will never learn), but can someone abstractly help me out?
I'm not sure what the point of the timer is. Do you want to run btnAutoMiner_Click each second or on each click? If you add on each click, what is the point of the timer? Sorry, it's just a bit hard to understand your goals.
Here is the timer documentation in case you needed it.
Edit: to perform a click from the timer, you can you use .PerformClick to simulate a click.
I mean this with the deepest respect but have you tried using the debugger and breakpoints to check if everything unfolds as expected.
I took the liberty to recreate your program, making a form that fits your code and what I got was a functional program that behaved as you described, when I click GetGold my gold increases by the goldPerClick value as expected, same with the UpgradeClick.
As I clicked Buy Auto-Miner I had a breakpoint at the buttons event method, the level went up by 1 as expected and the timer started just fine, again with a breakpoint I was monitoring the timers event method, which was called once per second as expected, so conclusion is that your program behaves just as its expected to logically.
However for the GUI there is a bit of a problem, the values is not being updated as the timer ticks so I would suggest looking there first, also some labels/buttons texts is only updated when certain buttons are clicked so again I would suggest putting all those in the same place and just call that method when needed.
Just some friendly design advice as well (without being too specific):
Consistency is important for good design, this means either use value1 += value2 or value1 = value1 + value2 both is equally as right but mostly for consistency.
Using the right value types, using int values for simple numbers like 10 (sbyte, byte, short, ushort, int, uint, or char) and use floating point values for values like 1.5 (Double, Float)
I really hopes these tips helps you along your way and good luck with the project.
Assuming that your GetGold button is working as you expect it to, you can programatically trigger it's click handler:
btnGetGold.PerformClick();
You would put this code inside the tick event handler for your Timer.
This is the image of the design window:
Here is the MainForm.Designer.cs file:
namespace SamsCSharp24
{
partial class ImeObrasca
{
// irrelavent code is omitted, only event subscriptions are left
private void InitializeComponent()
{
// irrelavent code is omitted for brewity
//
// SelectPicture
//
this.SelectPicture.Paint += new System.Windows.Forms.PaintEventHandler(this.SelectPicture_Paint);
this.SelectPicture.Click += new System.EventHandler(this.SelectPicture_Click);
//
// Quit
//
this.Quit.Click += new System.EventHandler(this.Quit_Click);
//
// PictureBox
//
this.PictureBox.MouseLeave += new System.EventHandler(this.PictureBox_MouseLeave);
this.PictureBox.MouseEnter += new System.EventHandler(this.PictureBox_MouseEnter);
//
// btnOptions
//
this.btnOptions.Click += new System.EventHandler(this.btnOptions_Click);
//
// timerClock
//
this.timerClock.Tick += new System.EventHandler(this.timerClock_Tick);
}
#endregion
}
}
Here is the MainForm.cs file:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SamsCSharp24
{
public partial class ImeObrasca : Form
{
public ImeObrasca()
{
InitializeComponent();
// when uncommenting below line, window is not seen in taskbar
// this.ShowInTaskbar = false;
}
// other code is omitted for brewity
private void SelectPicture_Paint(object sender, PaintEventArgs e)
{
// just for fun, change color of a button to light blue
SelectPicture.BackColor = Color.Azure;
}
private void timerClock_Tick(object sender, EventArgs e)
{
// when timer ticks, change label's text into current time of day
staticClock.Text = "Current time of day: " +
DateTime.Now.Hour.ToString() + " : " +
DateTime.Now.Minute.ToString() + " : " +
DateTime.Now.Second.ToString();
}
}
}
Timer control has following properties set via designer:
Enabled = true;
Interval = 1000
Name = timerClick
Tick (event) = timerClock_Tick
As for label, here are the properties also set with designer:
BorderStyle = FixedSingle
Name = staticClock
Autosize = false
Text =
Other properties are default or irrelevant ( like Location or Size )
PROBLEM:
When I run the application ( in Debug mode ), window appears with properly placed controls and with proper look. Every other part of the code works successfully ( picture opening / drawing etc ) but the label remains empty, as initially set in the designer.
After I minimize / maximize the window, the label text is set correct. I have tried to move the part of the window with label "out" of the screen and get it back to see what happens. The text in the label was changed sometimes -> it didn't update correct.
MY EFFORTS TO SOLVE THE PROBLEM:
This is my first time trying out C# and WinForms so I have tried to find some online documentation on timers.
After examining .Designer.cs file I have found out that the timer from toolbox belongs to System.Windows.Forms.Timer class. I found nothing there to help me, since in Remarks section is stated that setting property Enabled to true starts the timer, and setting it to false stops it.
I have tried to put simple message box, and it started popping properly when the window was minimized. When window is in normal state nothing showed, but other parts of the program worked well ( picture opening / drawing / etc ).
After trying to Google for solution / searching here on SO, I have found no concrete solution ( although some suggestions were made, but as I said, they weren't helpful to me ).
QUESTION:
How to modify timer's tick handler, so label's text can be modified every second?
What am I doing wrong?
As Hans Passant stated in one of the comments, "Paint event handlers should only paint, they should never change properties that cause the Paint event to be fired again. Such shenanigans cause the UI thread to burn 100% core, never getting to dispatch the low-priority synthesized messages. Like WM_TIMER. Minimizing the window stops that, temporarily."
Im trying to create a section of a program, where a user presses a button and a image is placed in one of 9 pictureboxes. Each time the button is clicked, a different picturebox should be selected. However, before i get to that, i'm having trouble getting my method to see the arrays i am trying to pass it.
I have 2 arrays, Slots and SlotsUsed and i am trying to initalise them when the program starts. However, when i try to pass them to the method "randomBox" which is called within "Button1" visual studio says they do not exist. How can i make these arrays visible throughout my code?
Many thanks
Anthony
using System;
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 pin_program
{
public partial class Mainscreen : Form
{
//Sets where users files are to be stored (for later use)
string activeDir = #"C:\Users\Tony\Downloads\Programs\pin program\Users";
public Mainscreen()
{
InitializeComponent();
}
//method to generate random number
private int RandomNumber(int min, int max)
{
Random random = new Random();
return random.Next(min, max);
}
public void randomBox(int pictureVal, PictureBox[] Slots, bool[] SlotsUsed)
{
//generate random number
int j = RandomNumber(0, 9);
if (SlotsUsed[j] == false)
{
// Create image, assign it and set slots value to used
Image newImage = Image.FromFile(#"C:\Users\Tony\Downloads\Programs\pin program\pin program\pin program\Images\" + pictureVal + ".jpg");
Slots[j].Image = newImage;
SlotsUsed[j] = true;
}
else
do
{
j = RandomNumber(0, 9);
} while (SlotsUsed[j] == false);
return;
}
private void button1_Click(object sender, EventArgs e)
{
//for use later
string userName = textBox1.Text;
//for use later
label1.Visible = true;
//test call of method.
randomBox(1, Slots, SlotsUsed);
}
public void Mainscreen_Load(object sender, EventArgs e)
{
//array for slots
PictureBox[] Slots = new PictureBox[9];
Slots[0] = pictureBox1;
Slots[1] = pictureBox2;
Slots[2] = pictureBox3;
Slots[3] = pictureBox4;
Slots[4] = pictureBox5;
Slots[5] = pictureBox6;
Slots[6] = pictureBox7;
Slots[7] = pictureBox8;
Slots[8] = pictureBox9;
//array for used slots
bool[] SlotsUsed = new bool[9];
for (int i = 0; i != (SlotsUsed.Length); i++)
{
SlotsUsed[i] = false;
}
}
}
}
EDIT:
I dont seem to be able to post comments for some reason, so i'll just ask here. How would i declare my arrays as instance variables instead of local? Does instance variable have another name i might know it by?
CHeers
Currently you're declaring Slots and SlotsUsed as local variables in Mainscreen_Load. They need to be instance variables in your form, as otherwise you can't refer to them elsewhere - and indeed they won't logically exist elsewhere. They're part of the state of your form, so they should be instance variables.
Additionally, your approach for generating random numbers is broken - see my article on random numbers for more information.
I'd also add that you might consider just using a single collection, shuffling it to start with, and then removing items from it as you go - that way you can easily tell when you've run out of images, and you don't have to loop round until you find an unused slot.
well, the easiest way to do this would be to declare a field.
protected PictureBox[] Slots
inside your Form class (outside of any methods.
I'm an extreme newbie at C#, but I've been slowly moving through the Head Start C# tutorial book (and finding it extremely enjoyable so far). However, I've hit a wall on the first "lab" assignment: They give code for controlling a PictureBox, and I can get that code to work on the main Form, but I can't get it to work from within a Class. I've gone back over the old lessons, and I've got a fairly good idea of what I'm missing, but for the life of me I can't figure out how to access the main Form's PictureBox from within my class (as the tutorial is telling me I should do).
It's a bit frustrating, because I didn't jump ahead in the book at all, but I'd swear we haven't covered this yet. Anyway, appealing to Real programmers.
Here's the code provided in the tutorial, in a section called "Your object can control things on your form" (p208 for anyone with the book).
Point p = MyPictureBox.Location
p.x += distance;
MyPictureBox.Location = p
Below I'm posting the relevant (I think?) parts of my code below. Button1 works for me when compiled, Button2 "works," in the sense that the current class just tells it to print the passed INT because I've commented out the code I can't get to work.
Thanks in advance!
Code for the Form1:
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
// Namespaces I'll need.
namespace Troubleshooting_PicBoxes
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent(); // Start all the Form1 stuff (all IDE-generated)
}
private void button1_Click(object sender, EventArgs e) //method from clicking the first button
{
int distance = 5; // Create this variable called "distance"
Point BoxMovement = MyPictureBox.Location; //create a point called BoxMovement
BoxMovement.X += distance; // Adjust the X of BoxMovement by my distance int.
MyPictureBox.Location = BoxMovement; // now adjust the Box by the Point's location.
}
private void button2_Click(object sender, EventArgs e)
{
PicMover PicMoverObject1 = new PicMover(); // Reserve Space for&Create object
PicMoverObject1.MoveThatPic(5); // Execute Object Method with a value of 5
}
}
}
Code for the PicMover class:
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Troubleshooting_PicBoxes
{
class PicMover
{
public void MoveThatPic(int distance) // New method,
// takes a variable called Distance.
{
MessageBox.Show(distance.ToString()); // Just show us that Variable.
// I need to be able to access Form1's picture box before I can use this. :(
/* Point BoxMovement = MyPictureBox.Location; //create a point called BoxMovement
BoxMovement.X += distance; // Adjust the X of that by distance.
MyPictureBox.Location = BoxMovement; // now adjust the Box by the Point's location.
*/
}
}
}
If you need to access something, why don't you just give it access? Like passing it as a argument to the method.
public void MoveThatPic(PictureBox picBox, int distance) // New method,
// takes a variable called Distance.
{
MessageBox.Show(distance.ToString()); // Just show us that Variable.
// I need to be able to access Form1's picture box before I can use this. :(
Point BoxMovement = picBox.Location; //create a point called BoxMovement
BoxMovement.X += distance; // Adjust the X of that by distance.
picBox.Location = BoxMovement; // now adjust the Box by the Point's location.
}
now in button2 click event handler:
private void button2_Click(object sender, EventArgs e)
{
PicMover PicMoverObject1 = new PicMover(); // Reserve Space for&Create object
PicMoverObject1.MoveThatPic(MyPictureBox, 5); // Execute Object Method with a value of 5
}
The tutorial code LOOKS like you are grabbing the location from your class (MyPictureBox.Location) then changing the location, then moving your object to that new location.
Point p = MyPictureBox.Location // Save the location of your object
p.x += distance; // Increase the distance
MyPictureBox.Location = p // Set your object to the new location
The second button press event is different. Perhaps you should be returning a location from the function? So, when you call the function, you set the PictureBox on the main form to the value returned.
If you want to create a general purpose class that can be accessed from any Form ... that creates an instance of it ... to move any PictureBox on that Form, then 'Deerchao's answer shows you how to do it.
Although ... consider ... every Form is going to have declare its own instance of PicMover; Form1's instance of PicMover is not "visible" to any other Form unless you also somehow publish it.
To put it more technically : the PicMover class, as defined here, exists in the Scope of the Application NameSpace; it's a template for creating type of object that every other Class in the Application can use to make an instance of, but that no Class in the Application "has" an instance of by default.
Here's an alternative to Deerchao's excellent answer that demonstrates "injection" : injection is appropriate when you want an instance of a class to "hold onto" a reference : in this example we say that the Form's instance of a PictureBox gets "bound" to an instance of the 'PicMover class :
We declare a Public Property within the class 'PicMover that will hold a reference to Form1's PictureBox :
public class picMover
{
// note use of C# 3.0 automatic property feature here
public PictureBox myPictureBox { get; set; }
public void movePic(int distance)
{
// note test for null here
if (myPictureBox != null)
{
myPictureBox.Left += distance;
}
}
}
So in Form1 after you create the instance of 'PicMover, you set its internal PictureBox Property, and then use its internal 'movePic method : like this :
// instance of PicMover created in the Form's scope
picMover myPicMover = new picMover();
private void Form1_Load(object sender, EventArgs e)
{
// when the Form loads inject the reference to the PictureBox instance into the instance of 'PicMover
myPicMover.myPictureBox = pictureBox1;
}
private void button1_Click(object sender, EventArgs e)
{
myPicMover.movePic(23);
}
Note that, imho, testing to make sure an "injected" reference to an object exists, by testing for null before using it is a good habit to get into.
Another way you can get an instance of the PictureBox object "bound" into an instance of 'PicMover is to pass the instance of the PictureBox to the Constructor of the class as a parameter : I bet by the time I finish posting this, someone else will have already posted an answer showing that technique. You might want to "inject" using a Public Property as shown here when you expect the internal reference to be changed vs. passing the PictureBox in to the Constructor of the Class when you don't expect it to be changed.
Another strategy is to make 'PicMover a public static class, with public static methods : then every Form can "see it," and there's no need for any form to make an instance of it (in fact you can't "instance" a static class if you wanted to : that's what a static class is).