C# Newbie Question from tutorial book: "Head Start C# Greyhound Lab" - c#

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).

Related

Accessing form controls from a different class

I am creating a windows form application using c#. My form has labels, list-boxes and buttons, all of which need to be edited. They are edited from a different class, and further will need the values in the labels to perform other logic.
My problem is accessing these controls from other classes. I tried making an instance of the form and using it like this:
Form frm = new Form();
myVar = frm.lblMylabel.Text;
However, using a breakpoint, I followed the code, and it loops between the above instance being created, and the start of the Form code, which calls the class again where the instance was created.
I have tried to find the answer online, however they don't seem applicable to what I am trying to do, and whilst I am unsure how to do it, they appear to be overly complex.
So, my question it: is there a relatively simple way to be able to access values and edit values from controls in a form from another class?
Thanks in advance.
EDIT - Breakpoint starts at Point 3, '=new Form();'.
Loops to Point 1.
Point 2 calls GamePlay().
Reaches Point 3 and loop restarts.
public partial class frmGame : Form //Point 1
{
public frmGame()
{
InitializeComponent();
Game.GamePlay(); //Point 2
}
class Game{
public static void GamePlay()
{
frmGame form = new frmGame(); //Point 3
form.lstPrevious1.Items.Add("Item Number");
}
}
Your problem has nothing to do with accessing properties of a different class, you are just producing a recursive loop:
At Point 3, within the method GamePlay you create a new instance of frmGame. When creating an instance (an object), its constructor is called. In this case, your constructor is the method public frmGame().
Within this constructor, you now call GamePlay (Point 2), which, as we saw from before, again creates a new instance of frmGame (Point 3 again), also again calling its constructor (Point 1).
At this point, you have already two forms created (although not shown yet).
Very soon you will receive a stack overflow.
One possible solution: Move the line creating your instance into a different method that is guaranteed to be called only once at program start:
public partial class frmGame : Form //Point 1
{
public frmGame()
{
InitializeComponent();
Game.GamePlay(); //Point 2
}
}
class Game{
public static void GameStart(){
frmGame form = new frmGame(); //Point 3
}
public static void GamePlay()
{
form.lstPrevious1.Items.Add("Item Number");
}
}

C# windows forms reference control - NullReferenceException

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.

c# objects modification: a strange behaviour

I'm developing a WPF C# application and I have a strange behaviour in modification of objects. I try to explain it in general way.
Suppose that you have an object of a class described as follows:
public class A
{
int one;
bool two;
List<B> listofBObjects;
}
where B is:
public class B
{
int three;
int four;
}
I pass an instance of A class and an instance of B class from a window to another, only defining two variables of type A and B in the second window and passing them before the Show() method, with the following code, executed into an instance of window FirstWindow:
SecondWindow newWindow = new SecondWindow();
newWindow.instanceOfA = this.instanceOfA; //instanceOfA is of type A
newWindow.instanceOfB = this.instanceOfA.listOfBObjects[0]; //instanceOfB is of type B
newWindow.Show();
If I have to repeat this code twice(that is, opening twice the window), in the first execution everything works as expected, infact if I modify values in instanceOfB variable, I see the modification also in instanceOfA variable. But, in the second execution, the modification in instanceOfB does not affect instanceOfA...
The modifications are done in newWindow. For example:
this.instanceOfB.three++;
this.instanceOfB.four--;
Imagine that you are in the FirstWindow. Click on a button and SecondWindow opens, passing both variables as described above. In SecondWindow, do some modifications, click on OK and SecondWindow closes, returning control to FirstWindow. If I reclick on the same button, I reopen SecondWindow. If I do modifications now, they do not affect both variables.
I try to have a look (in VS2012) at both variables in the console with control expression and I see that, in the first pass of code, both variables changes when code above is executed but, in the second pass of code, only instanceOfB changes...
EDIT:
Following the code that I use to pass parameters to SecondWindow...types are explaind below
IntermediatePosition obj = ((FrameworkElement)sender).DataContext as IntermediatePosition; //IntermediatePosition is Class B
IntermediatePositionsSettingsWindow ips = new IntermediatePositionsSettingsWindow();
ips.currentIntermediatePosition = obj;//this is the instanceOfB
ips.idxOfIpToModify = obj.index;
ips.currentSingleProperty = this.currentPropertyToShow; //this is the instanceOfA object
ips.sideIndex = this.sideIndex;
ips.ShowDialog();
Consider that obj is given by a button selection into a datagrid, in which each row represents an IntermediatePosition object. In the datagrid, there is a column button and, clicking by buttons, IntermediatePositionsSettingsWindow is opened with the proper data
EDIT:
I've performed the folloqing check:
this.currentPropertyToShow.sides[this.sideIndex].intermediatePositionList[i].Ge‌​tHashCode() == obj.GetHashCode()
where i is the index of related IntermediatePosition object. At first usage of IntermediatePositionsSettingsWindow the objects result equals, but in second usage they are different
Why this thing happens?
If it is needed any other clarification, I will edit the question
Thanks
It's difficult to give a proper answer to this, as there is insufficient code to correctly work out the issue. However, if you are databinding, then I believe you need to implement this interface. It is possible that you're issue is simply that you're model is not reflecting the changes to the screen.
I can't reproduce your problem. Here's a simplified representation of your class relation (as I understood from your question). Please let us know if this is correct:
public partial class MainWindow : Window
{
internal A instanceOfA;
internal B instanceOfB;
public MainWindow()
{
InitializeComponent();
instanceOfB = new B() { };
instanceOfA = new A() { listOfBObjects = new List<B>() { instanceOfB } };
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SecondWindow newWindow = new SecondWindow();
newWindow.instanceOfA = this.instanceOfA; //instanceOfA is of type A
newWindow.instanceOfB = this.instanceOfA.listOfBObjects[0]; //instanceOfB is of type B
newWindow.Show();
}
}
public partial class SecondWindow : Window
{
internal A instanceOfA;
internal B instanceOfB;
public SecondWindow()
{
InitializeComponent();
Loaded += SecondWindow_Loaded;
}
void SecondWindow_Loaded(object sender, RoutedEventArgs e)
{
MessageBox
.Show(String.Format("{0}",
this.instanceOfB == this.instanceOfA.listOfBObjects[0]));
this.instanceOfB.three++;
this.instanceOfB.four--;
}
}
Note: this is not an answer, just trying to establish some common ground for further discussions, as comments don't leave you enough freedom for code samples.
Thanks to #pm_2 and #BillZhang comments, I found a row in my code in which this.currentPropertyToShowwas edited. After the returning back at first window, infact, I perform the refresh of the window, but it is not needed to edit this.currentPropertyToShow, so I have commented it and everything works!
Thanks everybody for precious comments and suggestions!

Load previous session values when open new window

There is a MenuItem click event MainMenu_Define_Material which opens a window called Material. I get info from a textbox called txt_density and I save that info in density and return to main window via OK-button having click event Material_btn_OK_Click.
My question is, how I can avoid passing the last session values every time I open the window Material?
I want, once density is set, every time I open Material window I want to see the values of previous session.
private void MainMenu_Define_Material(object sender, RoutedEventArgs e)
{
newWin_material = new Material();
newWin_material.btn_OK.Click += new RoutedEventHandler(Material_btn_OK_Click);
if (density != -1)
{
newWin_material.txt_density.Text = density.ToString();
}
newWin_material.ShowDialog();
}
private void Material_btn_OK_Click(object sender, RoutedEventArgs e)
{
density = System.Convert.ToSingle(newWin_material.txt_density.Text);
newWin_material.Close();
}
Not very clear where that density comes from, but you can insert that field in uour data class what you can hold on data layer or may be like ObjectDataProvider for modelview.
There are a lor of different solutioms our there. The basic idea is:
Define common, shared data storage and keep there alll values you want to share between different windows.
Define a data class. I used here a static class with a static member Desnity.
public static class DataClass
{
public static double Desnsity;
}
After in any window you're able to access that field (read/write), like
DataClass.Density
Hope this is clear.
There is no concept of Session in WPF. You can try creating a static variable to do this or a app config value or pass a parameter via the constructor.
If you are going to new it ( e.g. new Material(); ) then it is going to start with zero information. That is what new does. What is wrong with passing the value in the ctor?
Try
MainWindow
{
private newWin_material = new Material(); // just new it ONCE
// this may need to be in MainWindow ctor.
newWin_material.btn_OK.Click += new RoutedEventHandler(Material_btn_OK_Click);

Providing multiple instances of a form yet processing events one at a time

I need to be able to let multiple instances of the same form be open as my application can be used in different places at once. On the other hand I need to be able to process the operations during the "OK" event one at a time to ensure data is stored safely and not overwritten by another form instance by accident.
I show my form using the .Show() method as I am using a few delegates in it:
private void newToolStripMenuItem_Click(object sender, EventArgs e)
{
bookingForm = new BookingForm(AddMemberBooking, AddUserBooking, CloseBooking);
bookingForm.Show();
}
I have tried to use the mutex to allow only one event of the OK button being pressed happen at a time, i have combined this with a Thread to meet the criteria i need.
When i click on the "OK" button I am given the following error:
Cross-thread operation not valid: Control 'comboBoxDay' accessed from a thread other than the thread it was created on.
This is the code for my booking form 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;
using System.Threading;
namespace Collection
{
//Allows the class to be serialized
[Serializable()]
public delegate void AddMemberBookingMethod(int date, int time, int mNo);
public delegate void AddUserBookingMethod(int date, int time, string fName, string lName, string pCode);
public delegate void CloseBookingFormMethod();
public partial class BookingForm : Form
{
public CloseBookingFormMethod CloseBookingForm;
public AddMemberBookingMethod AddMemberBooking;
public AddUserBookingMethod AddUserBooking;
private Mutex bookingMut = new Mutex();
private Thread thread;
public bool IsUser;
public BookingForm(AddMemberBookingMethod ambm, AddUserBookingMethod aubm, CloseBookingFormMethod cbfm)
{
InitializeComponent();
AddMemberBooking = ambm;
AddUserBooking = aubm;
CloseBookingForm = cbfm;
checkBoxMember.Checked = true;
//Control.CheckForIllegalCrossThreadCalls = false;
}
private void checkBoxUser_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxUser.Checked)
{
IsUser = true;
checkBoxMember.CheckState = CheckState.Unchecked;
textBoxMNo.Enabled = false;
textBoxFName.Enabled = true;
textBoxLName.Enabled = true;
textBoxPCode.Enabled = true;
}
else
{
IsUser = false;
checkBoxMember.CheckState = CheckState.Checked;
textBoxMNo.Enabled = true;
textBoxFName.Enabled = false;
textBoxLName.Enabled = false;
textBoxPCode.Enabled = false;
}
}
private void checkBoxMember_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxMember.Checked)
{
IsUser = false;
checkBoxUser.CheckState = CheckState.Unchecked;
textBoxFName.Enabled = false;
textBoxLName.Enabled = false;
textBoxPCode.Enabled = false;
}
else
{
IsUser = true;
checkBoxUser.CheckState = CheckState.Checked;
textBoxMNo.Enabled = false;
textBoxFName.Enabled = true;
textBoxLName.Enabled = true;
textBoxPCode.Enabled = true;
}
}
private void buttonOK_Click(object sender, EventArgs e)
{
this.thread = new Thread(new ThreadStart(MakeBooking));
this.thread.Name = "bookingThread";
this.thread.Start();
}
private void MakeBooking()
{
this.bookingMut.WaitOne();
int date = this.comboBoxDay.SelectedIndex;
int time = this.comboBoxTime.SelectedIndex;
if (IsUser)
{
string fName = textBoxFName.Text;
string lName = textBoxLName.Text;
string pCode = textBoxPCode.Text;
AddUserBooking(date, time, fName, lName, pCode);
}
else
{
int mNo = int.Parse(textBoxMNo.Text);
AddMemberBooking(date, time, mNo);
}
this.bookingMut.ReleaseMutex();
CloseBookingForm();
}
private void buttonClose_Click(object sender, EventArgs e)
{
CloseBookingForm();
}
}
}
I realise I may not be doing this in the most efficient way but time is a bit of a factor.
I've researched the error and have heard of using delegates and .Invoke() but I'm still not entirely sure how to fix it.
EDIT:
I've found this code snippet when searching for a fix to my problem. I don't understand where/how I would use it.
if(this.InvokeRequired)
{
this.Invoke(new MyEventHandler(this.CreateAForm()));
return;
}
EDIT2:
Seems the guy finally saw sense, by creating the from with the new word it apparently passes the criteria. I wish I'd have known this before trying to reinvent the wheel.
You are getting this exception because your thread is accessing controls. That's not legal, control properties must only ever be accessed from the UI thread. You're okay on the TextBox.Text property, that one happens to be cached. But not ComboBox.SelectedIndex. And closing the form from another thread is going to bomb too.
Your mutex has nothing to do with it, but keep it if you want to prevent threads from overlapping. Using a delegate's Invoke method isn't going to solve it, that just starts a thread as well. You'll need to collect the info that the thread is going to need in a little helper class and pass that as the argument to the Thread.Start() method.
Closing the form is a bit tricky too, the user might well have already closed it while the thread was running. That's going to cause an ObjectDisposed exception. A quick fix is to set the form's Enabled property to false so the user can't close it. You'll need to use the form's Invoke() method to ensure the closing is done on the UI thread.
Last but not least, if these threads don't take a lot of time (a second or so), consider not using threads at all and display a wait cursor instead.
One simple way to do this is to use the overload of the Thread.Start method that accepts an object: Thread.Start Method (Object). In this object you will store all the data/state necessary in order to make the update.
All the code that references the form and its controls needs to be moved into the OK click event method or refactored out to a method that just returns a data object. Then pass this object into the thread start method.
Some pseudo code:
on_click_event()
{
object data=getFormData();
thread.start(data);
}
There are better ways to do this but this is a quick fix for your code.
I think you could simply disable the OK buttons on other open forms to give users a visual cue. Then you shouldn't even have the issue. Provide a callback delegate to something in the application controller which knows which forms are open. Each form can provide a public method to disable the OK button. Disable to OK button on all the other forms.
I'm not really following your code too well. I would think the mutex could be outside of the form code in the first place (i.e. in the delegates that do the actual work), and if it is within a single application, you could just use the lock (object) method to ensure only one thread is executing a given bit of code.
I'd also like to add that a mutex is not going to stop multiple users on different machiens being able to click OK at the same time. I'm not sure if that's what you meant in your question by a form being run in different places.
I think that AddUserBooking and the other delegate should be responsible for ensuring that they are threadsafe and this should not be part of the UI. If they aren't threadsafe, why aren't they? It's relatively easy to make database commit functions each have their own connection to the database during their operations and thread-safety should not be an issue.

Categories

Resources