I'm a beginner at C# who's decided to create a textbased adventure game in a Winform, but I've been struggling with updating the form whenever it needs to update. In the past, I have used something.Invalidate(); to update an image, but apparently that doesn't work for an entire form.
I have a set of labels that display text based on an integer and whenever the value of the integer updates, I'd like the form to show that.
What I have tried thus far:
public partial class GameWindow : Form
{
public void buttonInventory_Click(object sender, EventArgs e)
{
Basic.HP = Basic.HP++;
this.Refresh();
}
}
While the HP updates, the form doesn't show it. Is there anything else I should use than Refresh();? A lot of googling mostly resulted in explanations about Backgroundworkers, but do I really need another thread for something as simple as this?
Why not just make a separate routine for updating values, that you call after every value change. IE: (Note - I don't program in C#):
public partial class GameWindow : Form
{
public void buttonInventory_Click(object sender, EventArgs e)
{
Basic.HP = Basic.HP++;
updateValues();
}
public void updateValues()
{
hp.text = HealthInteger;
basic.text = BasicInteger;
}
}
And call this for evey value change.
A label can't be bind to a string value like an image can be to a picture box.
The simplest solution here is too explicitly set the Text property of the label each time your HP property is changed :
private void RefreshFormWithModel(Basic basic)
{
labelHP.Text = basic.HP;
}
public void buttonInventory_Click(object sender, EventArgs e)
{
Basic.HP = Basic.HP++;
this.RefreshFormWithModel(Basic);
}
If you really want complex binding, here is some lectures.
I'm going to assume the class name of Basic is just Basic
in the class Basic
private int hp;
public int HP
{
get { return hp; }
set { hp = value; HP_Changed(); }
}
public event EventHandler HPChanged;
private void HP_Changed()
{
if (HPChanged != null) { HPChanged(this, new EventArgs()); }
}
in the GameWindow
//where ever you create a new Basic, add to the event handler
Basic Basic = new Basic();
Basic.HPChanged += Basic_HPChanged;
private void Basic_HPChanged(object sender, EventArgs e)
{
Basic b_sender = (Basic)sender;
int NewHealth = b_sender.HP;
//Update whatever value needs to be updated, here
}
Then whenever the HP of the Basic is changed, it will fire an event in the GameWindow to update the appropriate field.
Related
I'm new in C# programming.
I want to write a simple Form application where on a ListView after each action, I will be updated what is going on.
I wrote some code which should do the job.
The application is working good but the update on the ListView not.
namespace ReportingTool
{
public partial class Form1 : Form
{
public static Form1 form;
public Form1()
{
InitializeComponent();
form = this;
WriteToList("1", "Program started");
}
private async void button1_Click(object sender, EventArgs e)
{
WriteToList("2", "Opening Chrome Browser");
PageNavigation driver = new PageNavigation();
await driver.BrowserActions();
WriteToList("3", "Opening TalentLink page");
await driver.GoToTalentLinkPageAsync();
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
}
public void WriteToList(string id, string action)
{
form.Activate();
ListViewItem x = new ListViewItem(id);
x.SubItems.Add(action);
form.listView1.Items.Add(x);
form.listView1.Refresh();
}
}
}
Thank you for your help.
Unfortunately there is a bug (or lack of implementation) on ListView. Basically if you make modification to any item on your existing list, this update will not be visible on UI despite being updated in code. Somehow you have to force the refresh on ItemSource in ListView. The only way I've managed to do it is save existing List of items set ItemSource to null and add updated list to the ItemSource, so:
public void WriteToList(string id, string action)
{
form.Activate();
ListViewItem x = new ListViewItem(id);
x.SubItems.Add(action);
form.listView1.Items.Add(x);
form.listView1.Refresh();
List<YourItems> newList = new List<YourItems>();
newList = form.listView1;
form.listView.ItemSource = null;
form.listView.ItemSource = newList;
}
Also I don't think that setting Static field of your class is a good way of approaching it.
So i have 2 forms.
Form 1 is my main form, and form 2 is where I enter text in a textbox to be displayed on a label on form 1. Also the button to 'confirm' and actually change the entered text of my label is on form 2 which needs to stay that way.
for some reason this does not work.
Form 2 has a text-box and a button, when I press the button, it changes the string value of the designated string.
This string is linked to a label on form 1. the string is being changed so that is not the problem I confirmed this by a adding a button which pops up a message box showing the new string value.
While searching for an answer I found that is must be some sort of refreshing problem, I tried a lot of methods with no success. Only methods that did work where those who would put my button onto form 1 instead of 2.
I've been googling for 3 hours straight on how to fix this problem but either the methods don't work or they change my button from form 2 to my main form (form 1).
Please don't call me lazy I really can't find a method that works!
EDIT:
Code
GameScreen.cs
namespace _2pGame
{
public partial class GameScreen : Form
{
public GameScreen()
{
InitializeComponent();
P1NameLabel.Text = gm.P1Name;
P1ClassLabel.Text = gm.P1Class;
P2NameLabel.Text = gm.P2Name;
P2ClassLabel.Text = gm.P2Class;
}
private void PlayerInfoButton_Click(object sender, EventArgs e)
{
PlayerInfo playerinfoload = new PlayerInfo();
playerinfoload.Show();
}
}
}
PlayerInfo.cs
namespace _2pGame
{
public partial class PlayerInfo : Form
{
public PlayerInfo()
{
InitializeComponent();
}
public void ConfirmPlayerInfo_Click(object sender, EventArgs e)
{
gm.P1Class = P1ClassChoice.Text;
gm.P1Name = P1TextBox.Text;
gm.P2Class = P2ClassChoice.Text;
gm.P2Name = P2TextBox.Text;
}
}
}
Refs.cs
namespace _2pGame
{
public partial class gm
{
public static string
P1Class,
P2Class,
P1Name,
P2Name;
}
}
An approach to this very well know situation is through delegates....
In your PlayerInfo form declare
public partial class PlayerInfo : Form
{
// define the delegate type (a parameterless method that returns nothing)
public delegate void OnConfirmPlayer();
// declare a public variable of that delegate type
public OnConfirmPlayer PlayerConfirmed;
.....
public void ConfirmPlayerInfo_Click(object sender, EventArgs e)
{
gm.P1Class = P1ClassChoice.Text;
gm.P1Name = P1TextBox.Text;
gm.P2Class = P2ClassChoice.Text;
gm.P2Name = P2TextBox.Text;
// Check is someone is interested to be informed of this change
// If someone assign a value to the public delegate variable then
// you have to call that method to let the subscriber know
if (PlayerConfirmed != null)
PlayerConfirmed();
}
}
Then in your GameScreen form, just before showing the PlayerInfo form, set the public PlayerInfo.PlayerConfirmed to a method into the GameScreen form class
private void PlayerInfoButton_Click(object sender, EventArgs e)
{
PlayerInfo playerinfoload = new PlayerInfo();
// Subscribe to the notification from PlayerInfo instance
playerinfoload.PlayerConfirmed += PlayerHasBeenConfirmed;
playerinfoload.Show();
}
// Method that receives the notification from PlayerInfo
private void PlayerHasBeenConfirmed()
{
P1NameLabel.Text = gm.P1Name;
P1ClassLabel.Text = gm.P1Class;
P2NameLabel.Text = gm.P2Name;
P2ClassLabel.Text = gm.P2Class;
}
This approach has the advantage to avoid a coupling between the GameScreen and the PlayerInfo. No need to know inside the PlayerInfo the existance of a GameScreen form and the name of its properties. You just publish a delegate that a subscriber could register to be informed of the changes and let the subscriber acts on its own code.
You need a reference to your main form and assign the textbox values each time they need to be updated.
public partial class PlayerInfo : Form
{
private readonly GameScreen _main;
public PlayerInfo(GameScreen main)
{
_main = main;
InitializeComponent();
}
public void ConfirmPlayerInfo_Click(object sender, EventArgs e)
{
gm.P1Class = P1ClassChoice.Text;
gm.P1Name = P1TextBox.Text;
gm.P2Class = P2ClassChoice.Text;
gm.P2Name = P2TextBox.Text;
main.P1NameLabel.Text = gm.P1Name;
main.P1ClassLabel.Text = gm.P1Class;
main.P2NameLabel.Text = gm.P2Name;
main.P2ClassLabel.Text = gm.P2Class;
}
}
You also need to pass the reference when the PlayerInfo form is created
private void PlayerInfoButton_Click(object sender, EventArgs e)
{
PlayerInfo playerinfoload = new PlayerInfo(this); //pass ref to self
playerinfoload.Show();
}
Note that there are other better ways to do this, but this is the easiest that I can think of.
You can probably look at events or Mediator pattern if you want something better.
I have two Forms: MainForm and OptionsForm, wich has a button (OK) that applies the some changes on MainForm. When I open the OptionsForm for the first time everything is ok, with the default values.
After I make some changes and and click OK the options are applied but when I open the OptionsForm for the second time, I wanted to hold the previous values, not the default ones like its happening.
OptionsForm is opened through MainForm like this
OptionsForm formOptions = new OptionsForm();
if (formOptions.ShowDialog(this) == DialogResult.OK)
{
// etc..
}
//...
public string otherLabel
{
get { return formMainLabel.Text; }
set { formMainLabel.Text = value; }
}
In OptionsForm I have a NumericUpDown and want to hold its value
private MainForm mainForm = null;
public OptionsForm(Form callingForm)
{
mainForm = callingForm as MainForm;
InitializeComponent();
}
// ...
private void btnOK_Click(object sender, EventArgs e)
{
this.mainForm.someLabel= someBox.Value.ToString(); // NumericUpDown
this.mainForm.otherLabel = "abc"; //>>> Getting NullReferenceException
this.Close();
}
Now I can hold my settings but I'm getting a NullReferenceException. I tryed this but it's still not working. Any sugestion?
ShowDialog() was already made to support this. It is different from Show(), other than it being modal, it also prevents the form object from being disposed when the user closes it. So you can simply call ShowDialog() again, the controls keep their original values:
private OptionsForm options = new OptionsForm();
private void button1_Click(object sender, EventArgs e) {
if (options.ShowDialog(this) == DialogResult.OK) {
// etc..
}
}
protected override void OnFormClosed(FormClosedEventArgs e) {
options.Dispose();
base.OnFormClosed(e);
}
Well, you should pass these values to OptionsForm, feel free to write custom constructor for OpptionsForm and call it when you need.
One way to do it would be to just always keep a reference to your FormOptions and show the same instance rather than creating a new one every time. Or, if you don't want to do that, you can create an Options class that stores all of your options, which can then be stored and passed into any new instance you create thereafter. There's advantages and disadvantages to both so feel free to choose the option that best suits your needs.
public class MyOptions
{
public String StringOption { get; set; }
public int IntOption { get; set; }
}
Your FormOptions would then have a MyOptions property where you can set all of your options and retrieve them.
public class FormOptions : Form
{
...
private MyOptions _options;
public MyOptions Options
{
get { return _options;}
set
{
_options = value;
// Set the Form's control values accordingly.
}
}
...
}
And finally, you would call it like so in your code:
FormOptions optionsForm = new FormOptions();
MyOptions savedOptions = new MyOptions(); // Probably don't want to create a new instance every time but I'm sure you get the idea here.
optionsForm.Options = savedOptions;
optionsForm.ShowDialog();
// Get the new options after the form is closed.
savedOptions = optionsForm.Options;
if you just want to keep only one numeric up/down control's value, then just pass it as a parameter in the constructor..
public FormOptions(MainForm -mainFrm, int curNumericValue)
{
someBox.value = curNumericValue;
}
and instantiate the form with the value from the MainForm
private void button1_click(....)
{
FormOptions formOptions = new FormOptions(this, Convert.toInt32(someLabel.text));
formOptions.ShowDialog();
}
Why not just make use of the "Settings" that are available within the build properties, and WinForms itself? Just have your settings form populate itself with these variables, and "set" them when you click OK. Then have the MainForm refresh from these settings once the SettingsForm has fully closed. Easy, no mess, and no passing variables between forms.
Overview: http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx
SO Question covering Settings: Save Settings in a .NET Winforms Application
It also has the added bonus that the values will be saved between sessions of the application.
Seems like you are calling an instance of the options form from a click event like this:
button1_click(object sender, EventArgs e)
{
OptionsForm optForm = new OptionsForm();
optForm.showDialog();
}
You have to create variable for the options form class within the Mainform class, instantiate it there, or in the constructor, and only call the ShowDialog() or Show() method within the button_Click event. Like this:
partial class MainForm:Form
{
OptionsForm optForm;
............
............
public MainForm() //Constructor
{
initialiseComponent();
optForm = new OptionsForm();
........
}
.......
private button1_Click(object sender, EventArgs e)
{
optForm.Show(); // or ShowDialog()
}
}
and use this.hide() instead of this.close() in the options form.. or else the form gets disposed..
I'm new to event programming, and I'm obviously misunderstanding something that I'm trying to do.
I have a Windows Forms application that subscribes to events from another class. T
//Class that provides event handler to Windows Forms application.
class Foo
{
public string Value{get; set;}
// Lots of other code
public void OnEventFired(object sender, EventArgs e)
{
// Attempt to access variable Value here.
}
}
From the Windows Form code I'm first setting the variable Value in class Foo before triggering the event that will execute the code in OnEventFired above.
What I'm seeing is that when used in the event handler the variable Value doesn't contain the value that was set before the event was fired (Value is null).
I know I can extend EventArgs to include the variable data, but I'm trying to understand why what I'm doing doesn't work.
Here's a short example which works. Compare this to your code to work out what's wrong.
using System;
using System.Windows.Forms;
class Foo
{
public string Value { get; set; }
public void HandleClick(object sender, EventArgs e)
{
((Control)sender).Text = Value;
}
}
class Program
{
public static void Main()
{
Foo foo = new Foo { Value = "Done" };
Button button = new Button { Text = "Click me!" };
button.Click += foo.HandleClick;
Form form = new Form
{
Controls = { button }
};
Application.Run(form);
}
}
My guess is that you've hooked up the event handler using a different instance of Foo than the one you've set Value in. For example, like this:
Foo foo = new Foo { Value = "Done" };
...
// Different instance of Foo!
button.Click += new Foo().HandleClick;
... but it's hard to tell without seeing any more code.
The only reason that you can not access the variable Value is
Value is not set
You are binding event to a different instance, not the one with the Value been set.
The best would be to get the Value in constructor, so that it is guaranteed that the Value is set.
class Foo
{
public string Value { get; set; }
public Foo(Value value)
{
}
public void HandleClick(object sender, EventArgs e)
{
((Control)sender).Text = Value;
}
}
This is my first C# application, entirely self-taught without any prior software programming background. I did some research on Undo/Redo but could not find anything helpful (or easy to understand). Therefore, I'm hoping someone can help me in designing undo/redo function for my program (winforms application). The application consist of a main form where subsequent child forms will be called to record user specified values during certain events (button clicks etc). After every event is handled, a bitmap will be drawn in buffer and then loaded to a picturebox within the main form during the OnPaint event of the main form. Each input in separated into custom class objects and added into separate List and BindingList. Objects contained within List are used for graphics (to indicate coordinates etc) while objects in BindingList are used to display some important values on DataGridView. Just to give a brief description, the codes look something like this:
public partial class MainForm : form
{
public class DataClass_1
{
public double a { get; set; }
public double b { get; set; }
public SubDataClass_1 { get; set; }
}
public class SubDataClass_1
{
public double x { get; set; }
public double y { get; set; }
public string SomeString { get; set; }
public CustomEnum Enum_SubDataClass_1 { get; set; }
}
public class DisplayDataClass
{
public string SomeString { get; set; }
public double e { get; set; }
public double f { get; set; }
}
public enum CustomEnum { Enum1, Enum2, Enum3 };
// Lists that contain objects which hold the necessary values to be drawn and displayed
public List<DataClass_1> List_1 = new List<DataClass_1>();
public List<DataClass_2> List_2 = new List<DataClass_2>(); // Object has similar data types as DataClass_1
public BindingList<DisplayDataClass> DisplayList = new BindingList<DisplayDataClass>();
Bitmap buffer;
public MainForm()
{
InitializeComponent();
dgv.DataSource = DisplayList;
}
private void DrawObject_1()
{
// some drawing codes here
}
private void DrawObject_2()
{
// some drawing codes here
}
protected override void OnPaint(PaintEventArgs e)
{
DrawObject_1();
DrawObject_2();
pictureBox1.Image = buffer;
}
// Event to get input
private void action_button_click(object sender, EventArgs e)
{
ChildForm form = new ChildForm(this);
form.ShowDialog();
Invalidate();
}
}
The child forms' codes look something like this:
public partial class ChildForm : form
{
public ChildForm(MainForm MainForm)
{
InitializeComponent();
// Do something
}
private void ok_button_click(object sender, EventArgs e)
{
DataClass_1 Data_1 = new DataClass_1();
DisplayDataClass DisplayData = new DisplayDataClass();
// Parsing, calculations, set values to Data_1 and DisplayData
MainForm.List_1.Add(Data_1);
MainForm.DisplayList.Add(DisplayData);
this.DialogResult = System.Windows.Forms.DialogResult.OK;
this.Close();
}
}
Since all necessary data are stored in the lists and will only be changed after certain events are triggered (mostly button clicks), therefore I tried to use these lists to determine the state of the application during run time. My approach in implementing the undo/redo function is by adding the following codes:
public partial class MainForm : form
{
public class State()
{
public List<DataClass_1> List_1 { get; set; }
public List<DataClass_2> List_2 { get; set; }
public BindingList<DisplayDataClass> DisplayList { get; set; }
// and so on
public State()
{
List_1 = new List<DataClass_1>();
List_2 = new List<DataClass_2>();
DisplayList = new BindingList<DisplayDataClass>();
}
}
State currentState = new State();
Stack<State> undoStack = new Stack<State>();
Stack<State> redoStack = new Stack<State>();
private void MainForm_Shown(object sender, EventArgs e)
{
// Saves original state as first item in undoStack
undoStack.Push(currentState);
}
protected override void OnPaint(PaintEventArgs e)
{
// Update lists from currentState before drawing
List_1 = new List<DataClass_1>(currentState.List_1);
List_2 = new List<DataClass_2>(currentState.List_2);
DisplayList = new BindingList<DisplayDataClass>(currentState.DisplayList);
}
// When undo button is clicked
private void undo_button_Click(object sender, EventArgs e)
{
if (undoStack.Count > 0)
{
redoStack.Push(currentState);
undoStack.Pop();
currentState = undoStack.Last();
Invalidate();
}
}
// When redo button is clicked
private void redo_button_Click(object sender, EventArgs e)
{
// Have not thought about this yet, trying to get undo done first
}
// Events that trigger changes to values held by data objects
private void action_button_Click(object sender, EventArgs e)
{
// Replace the following code with previously stated version
if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
ChildForm form = new ChildForm(this)
UpdateState();
undoStack.Push(currentState);
Invalidate();
}
}
// To update currentState to current values
private void UpdateState()
{
currentState.List_1 = new List<DataClass_1>(List_1);
currentState.List_2 = new List<DataClass_2>(List_2);
currentState.DisplayList = new BindingList<DisplayDataClass>(DisplayList);
// and so on
}
}
Result:
The application does not perform the undo function correctly. The program shows the correct output under normal conditions but when the undo event is triggered, regardless of how many objects have been drawn, the application reverts back to initial state (the state where there is no recorded data). I've used System.Diagnostics.Debug.WriteLine() during events where the stack is changed to check the number of counts within undoStack and it seems to give the correct counts. I'm guessing that the lists need to be copied/cloned in a different manner? Or am I doing something wrong here? Can anyone please guide me? Performance, readability, resource management, future maintenance and etc need not be considered.
There are a lot of approaches that will work, each with different strengths and weaknesses, but I generally like to define an abstract Action class and then a separate UndoRedoStack class.
The Action class would have two methods (Do and Undo) which each subclassed Action can implement. You isolate any logic that can "change state" to these Action subclasses thereby keeping that logic neatly encapsulated.
The UndoRedoStack is like a regular stack except with three core methods.
ApplyAction (like Push)
UndoAction (like Pop, but be sure to only
move the pointer/index without truncating or throwing away any
existing actions).
RedoAction (like Push, but you use the next value
already in the underlying stack/list instead of pushping/inserting a
new one).
Usually I find the biggest challenge then becomes designing each Action subclass in such a way that it maintains enough information to both undo and redo itself. But being able to encapsulate all state manipulation logic to individual Action subclasses usually makes it easiest for me to maintain in the long run.
You are storing reference objects in your stacks. If you want your method to work, you need to implement a clone() method in your state object, and store a new clone each time, otherwise, changes made are made to each member of the stack, as they all point to the same reference object.