Adding Controls to Form from another class - c#

I have a program, which creates one pictureBox in Form1, and then creates an instance of a class that I called InitialState. The InitialState puts the source to the Image so that it is displayed, and after some time has passed, for which I used a Timer, it creates the next class, MainMenuState. Now, in that MainMenuState class that I've created, I would like to create another pictureBox and make it display on that Form1. Later on, I would like to make the pictures inside it change a bit, and then (possibly) destroy that pictureBox. After that, the program enters the next state (which is in yet another class), and again I would like that class to add a picture box to the original form, and so on.
Basically, I would like to dynamically add controls to the main Form1, but not in the said form, but from the classes I create later on. I've been searching on the internet for a way to do that, and it seems like I would have to use a delegate in order to invoke the Controls.Add method of the Form1 class. I've tried that, and the code compiles, but the pictureBox still doesn't show up.
Here's my code:
Form1 class:
public const string RESOURCE_PATH = "C:/Users/Noel/Documents/Visual Studio 2010/Projects/A/Resources/Animations/";
public Form1()
{
InitializeComponent(); //here, the first pictureBox shows
iInitializeComponent();
zacetnaAnimacija.Dock = DockStyle.Fill; //zacetnaAnimacija is the first pictureBox that appears
zacetnaAnimacija.Anchor = AnchorStyles.Top | AnchorStyles.Left;
zacetnaAnimacija.SizeMode = PictureBoxSizeMode.StretchImage;
InitialState intialState = new InitialState(this, zacetnaAnimacija); //entering InitialState
}
InitialState class:
class InitialState : State
{
System.Timers.Timer initialTimer;
PictureBox pictureBox1;
Form1 form;
public InitialState (Form1 form, PictureBox pictureBox1) {
this.form = form;
GifImage zacetnaSlika = new GifImage(Form1.RESOURCE_PATH + "Presenting.gif"); //this is just a .gif picture I'm displaying
Image trenutnaSlika = zacetnaSlika.GetFrame(0); //a method that plays the .gif
pictureBox1.Image = trenutnaSlika; //makes the first .gif display
this.pictureBox1 = pictureBox1;
initialTimer = new System.Timers.Timer(2500);
initialTimer.Enabled = true;
initialTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
initialTimer.Enabled = false;
MainMenuState menuState = new MainMenuState(form, pictureBox1); //enters main menu state with the Form1 argument passed on
}
MainMenuState class:
class MainMenuState : State
{
Form1 form;
public MainMenuState (Form1 form, PictureBox pictureBox1) {
this.form = form;
GifImage zacetnaSlika = new GifImage(Form1.RESOURCE_PATH + "animated.gif");
Image trenutnaSlika = zacetnaSlika.GetFrame(0);
pictureBox1.Image = trenutnaSlika; //this simply makes another .gif appear in the picture box instead of the first one
PictureBox a = new PictureBox(); //HERE'S my problem, when I want to add ANOTHER pictureBox to that form.
a.BackgroundImage = trenutnaSlika;
a.Location = new System.Drawing.Point(0, 0);
a.Name = "zacetnaAnimacija";
a.Size = new System.Drawing.Size(150, 150);
a.TabIndex = 1;
a.TabStop = false;
AddControl(a); //calling the delegate
}
public delegate void AddControls(PictureBox a);
public void AddControl(PictureBox a)
{
if (form.InvokeRequired)
{
AddControls del = new AddControls(AddControl);
form.Invoke(del, new object[] { a });
}
else
{
form.Controls.Add(a);
}
}
As I've said, the code compiles, but it doesn't create the PictureBox a on the Form1, when the MainMenuState is created. The thing is, if I don't use the delegate in the MainMenuState and just try to do something like form.Controls.Add(a), then I get a "cross-thread operation not valid" exception, and it doesn't even compile. That's why I used the delegate, but even now, it doesn't work.
Can someone please help me?

initialTimer = new System.Timers.Timer(2500);
That's part of the reason you're having trouble. The Elapsed event runs on a threadpool thread, forcing you to do the BeginInvoke song and dance. Use a System.Windows.Forms.Timer instead, its Tick event runs on the UI thread.
You'll also run into trouble with memory management, these classes need to implement IDisposable.

Oh my God, I just found the reason X_x
It was the fact that since the first pictureBox was covering the entire form, and the second one, which was created by the delegate, showed behind it! I just need to bring it to front!
Thank you guys, nonetheless, I probably wouldn't have come to that without you.
Edit: However, may I ask how to bring that control to the front? The a.BringToFront() function doesn't seem to work.

Instead of
form.Invoke(del, new object[]{a});
try:
form.Invoke(new ThreadStart(delegate
{
form.Controls.Add(a);
}
));

Related

Disallowing interaction with background form

On my application's first run, two forms open. The topmost form needs to take priority, and disallow any interaction with the form in the background. I have tried ShowDialog() as referenced here, however this hides the form in the background which I do not wish to do. Is there a method of accomplishing this?
public Form1()
{
InitializeComponent();
if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
void firstrunactions()
{
//open the get-started form and invite user to populate serialisable objects
firstrun frwindow = new firstrun();
frwindow.ShowDialog();
}
When you are using .ShowDialog() the execution of the containing method is paused until you close the newly opened window. So make sure to do everthing else before you call .ShowDialog(). Otherwise your program gets stuck in this method. If you are calling .ShowDialog() before the background window is shown will cause problems.
But using .ShowDialog() here is totally correct and has the right functionality.
Example how not to do it (causes the same behavior like in your problem):
public Form1()
{
InitializeComponent();
//this is the wrong place for showing a child window because it "hides" its parent
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
The magical place where it works:
private void Form1_Shown(object sender, EventArgs e)
{
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
Edit: In your case it is enough moving if(!fileexistst...) into the Form1_Shown()-event.
Try with frwindow.ShowDialog(this);
Or instead "this" pass the other form as parameter.
Also move this part if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
in OnLoad override.

How to make a button on a form be enabled from a different form?

Im making a game with a menu that opens new forms for each level. When you complete a level, it is supposed to congratulate you through a message box (it does) and then enable the next level's button on the different menu form. So I accessed the designer and made the buttons public and tried:
new LevelMenu().button2.Enabled = true;
But that didn't work.
I also tried doing:
public event Action levelCompleted;
//then down lower i did (after it is declared that you won the level):
if (levelCompleted != null)
levelCompleted();
W1L1.levelCompleted += () => LevelMenu.button2.Enabled = true;
But that give me the error of:
An object reference is required for the non-static field, method, or property 'The_Levels.W1L1.levelCompleted'
The form i'm working with is "W1L1", and "LevelMenu" is the menu form. Thanks in advance
You basically need a reference to your instance of LevelMenu.
So when you create your "W1L1" form, you might just pass the LevelMenu to it.
public class W1L1
{
private readonly LevelMenu _levelMenu;
public W1L1(LevelMenu levelMenu)
{
this._levelMenu = levelMenu;
}
//Where you want to enable the button
this._levelMenu.button2.Enabled = true;
}
It's not the best solution, in an architectural way, but it works.
But it would be better if you create a more OOP way for enabling the button of the next level.
When you open the next level open it as a separate object as a new form. Now when the level is completed you can access the public controls on that form from the originating class. Something like this might help:
LevelMenu NextLevel = new LevelMenu();
public event Action levelCompleted;
if (levelCompleted != null)
levelCompleted();
NextLevel.button2.Enabled = true;
here's what I did I hope this helps
I created 2 Forms
Form1 = where menu is (buttons), Form2 = the game level (i.e. level 1)
then in Form2 I added an even LevelCompleted that will notify form1 that the player completed the level
//add this to form2
//the delegate
public delegate void LevelCompleted(Int32 level);
//the event
public event LevelCompleted LevelCompletedEvent;
then on Form1 (the menu form) when you create an instance of Form2 (which has the event) subscribe to it and create a handler, in my case I added it after i created the instance of Form2
private void button1_CLick(object sender, EventArgs e)
{
Form2 level1 = new Form2();
level1.LevelCompletedEvent += new Form2.LevelCompleted(level1_LevelCompletedHandler);
level1.Show();
}
//and this is the handler method
void level1_LevelCompletedHandler(int level)
{
//the logic for controlling the button states
// the level parameter can be used to indicate what is the current level completed.
if(level == 1)
{
button1.Enabled = false;
button2.Enabled = true;
}
}
Note: that in Form2 (the game level) I created a field gameOver that can be used if he did not complete the game
If in case he is permitted to go to next level, You must raise the event in this form to notify Form1 (the menu)
that he (the user) completed the level and Form1 will execute the method level1_LevelCompletedHandler(int level).
I know this is not well explained but I hope I can give you an idea on the event.

Deleting old winform and tray icon when I make a new winform

In my application I have two winforms. The first acts as my control panel and the second I use to take screen shots. However, when I go from Winform 2 back to Winform 1 I have a new winform created and a brand new tray icon. This is on top of the initial ones I create when the program first starts.
When I go from winform 1 to winform 2 I do the following:
this.Hide();
Form2 form2 = new Form2();
form2.InstanceRef = this;
form2.Show();
Then when I want to go back from Winform 2 to Winform 1 I do the following:
this.Close();
Winform1 form;
form = new Winform1 (capturedImageObj);
form.Show();
I know straight off the bat the issue falls on the fact I'm creating a new Winform1, but I need to do that so I can pass my capturedImageObj back into Winform 1.
I've tried calling this.close() and this.dispose() in the my first section of code but that only closes the program down. Is there a way I can dispose of Winform 1 but still use Winform 2 and pass the object I need to back into a new copy of Winform 1?
Here is the constructor for my Winform 1:
public ControlPanel(Image imjObj)
{
InitializeComponent();
_screenCap = new ScreenCapture();
_screenCap.OnUpdateStatus += _screen_CapOnUpdateStatus;
capturedImage = imjObj;
imagePreview.Image = capturedImage;
}
Change Winform1 to use properties like this:
public Image CapturedImage {
get { return imagePreview.Image; }
set { imagePreview.Image = value; }
}
Then change your constructor like this:
public ControlPanel(Image imjObj)
{
InitializeComponent();
_screenCap = new ScreenCapture();
_screenCap.OnUpdateStatus += _screen_CapOnUpdateStatus;
CapturedImage = imjObj;
}
And, finally, change your Winform2 to do this:
((Winform1)this.InstanceRef).CapturedImage = capturedImageObj;
this.InstanceRef.Show();
this.Close();
Based on the comment, it sounds like your InstanceRef property is of type Form. Change it to be of type Winform1. Or, I changed the code above to do some casting for you.

Method executing several times even though it is called once

I am doing a project which includes dynamic controls creation and removal from the WinForm,
So I decided to that part on a small test project.
Test project has two files Form1.cs and NewControls.cs. This program creates additional buttons whenever user clicks an Add button already on the form.And removes the newly created button when it is clicked (self removal button). Also after removal of button other button's Name, Text and their position are changed according to a local variable (controlIndex).
Form1.cs
public partial class Form1 : Form
{
static List<NewControl> newControlsList = new List<NewControl>();
public Form1()
{
InitializeComponent();
}
private void Add_Click(object sender, EventArgs e)
{
newControlsList.Add(new NewControl(newControlsList.Count));
}
public static void RemoveButton(object sender, EventArgs e)
{
NewControl tempNewControl = (NewControl)(sender as Button).Tag;
tempNewControl.RemoveControl();
newControlsList.Remove(tempNewControl);
MessageBox.Show("Removed!");
foreach (NewControl tempcontrol in newControlsList)
{
tempcontrol.controlIndex = newControlsList.IndexOf(tempcontrol);
tempcontrol.PlaceControl();
}
}
}
NewControl.cs
class NewControl
{
public int controlIndex = 0;
Button newButton = new Button();
public NewControl(int index)
{
controlIndex = index;
PlaceControl();
}
public void RemoveControl()
{
newButton.Dispose();
Form1.ActiveForm.Controls.Remove(newButton);
}
public void PlaceControl()
{
newButton.Tag = this;
newButton.Name = "btn" + controlIndex.ToString("D2");
newButton.Text = "btn" + controlIndex.ToString("D2");
newButton.Size = new Size(100, 20);
newButton.Left = controlIndex * 100;
Form1.ActiveForm.Controls.Add(newButton);
newButton.Click += new EventHandler(Form1.RemoveButton);
}
}
Program works nearly as expected. Problem is the MessageBox which I used in form1.cs in RemoveButton() fires many time (as opposed to just one time), which implies whole method being executed several times. Actually I pasted that MessageBox for debugging (sort of).
Since I cannot debug the application as when "Form1.ActiveForm.Controls.Add(newButton);" statement is executed, debugger Throws NullReferenceException, as there is not an active form while debugging.
I know that's like a bonus question but I thought to just put it there. I am a beginner and can't see the way through both the problems. 1st problem is really important for my original project as it will cause problem when many controls are added.
I think it is because you call PlaceControl from Form1.cs AND in the constructor of the NewControl class, Because you say newButton.Click += new EventHandler(Form1.RemoveButton);.
You are adding EventHandlers, so there can be more of them.
So when you call placeControl multiple times, you've got multiple event handlers, i think.
Probably the EventHandler hasn't been removed by RemoveButton. (I've been working in java most recently so my terms might be a little off for C#.) Suggestion: set control visibility to true when you want it and false otherwise rather than adding and removing.
Everytime a button is removed you go over your existing list of controls, and you call "PlaceControl", which attaches yet another handler.
foreach (NewControl tempcontrol in newControlsList)
{
tempcontrol.controlIndex = newControlsList.IndexOf(tempcontrol);
tempcontrol.PlaceControl();
}
Remove the above code block from RemoveButton, and you will see that your dynamically added buttons will each only trigger the event once.
In your RemoveButton event you loop on each button and call again PlaceControl.
The only reason is to reposition the remainder controls.
I think it's better a call to a separate method that do only this work.
public void RepositionControl()
{
newButton.Left = controlIndex * 100;
}
this will prevent to mess with event handlers added more than one time

Changing the property of a control on another form

Basically, I have a settings window, and when you click "OK", it's suppose to apply settings to the main form (eg, set font of a control, etc), and then close.
frmmain frm = new frmmain();
frm.OLVAltBackColor = Color.Aquamarine ;
I tried that, but it only applies the settings to that instance, and you can see it if you do frm.Show();
I'm trying to make it so the already opened form has it's control's properties changed.
What you are trying to do is not working because you are creating a NEW instance of your main form and updating that rather than the first instance. It is possible to update the main form by keeping a reference to it in your settings form... but...
...it sounds like you are approaching this from the wrong direction.
Don't make the settings form dependent on the main form. Instead create the settings form from the main dialog.
class SettingsForm : Form
{
// You need to ensure that this color is updated before the form exits
// either make it return the value straight from a control or set it
// as the control is updated
public Color OLVAltBackColor
{
get;
private set;
}
}
In your main form
(I'm assuming some kind of button or menu click)
private void ShowSettingsClicked(object sender, EventArgs args)
{
using (SettingsForm settings = new SettingsForm())
{
// Using 'this' in the ShowDialog parents the settings dialog to the main form
if (settings.ShowDialog(this) == DialogResult.OK)
{
// update settings in the main form
this.OLVAltBackColor = settings.OLVAltBackColor;
}
}
}
Apply the property change to the form that already exists and is already shown instead of creating a new form and changing that one.
In this code you're creating a new instance of the frmmain. Any changes you make to that new object will happen in the new object, not the one you actually want to change.:
frmmain frm = new frmmain(); //Creating a new object isn't the way.
frm.OLVAltBackColor = Color.Aquamarine ;
What you're looking for is a way to call on the already existant frmmain class and change the property of that.
Edit, for example:
using System;
class Statmethod
{
//A class method declared
static void show()
{
int x = 100;
int y = 200;
Console.WriteLine(x);
Console.WriteLine(y);
}
public static void Main()
{
// Class method called without creating an object of the class
Statmethod.show();
}
}

Categories