Timer doesn't seem to tick ( until I minimize window ) - c#

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

Related

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.

Add timer to idle game

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.

How to click through a WinForm, without taking focus?

I cant find any information for this...
Basically I have a TopMost WinForm in C# that is an overlay for another application.
The overlay has buttons that I need to be able to press without stealing focus from the other application.
Is this possible, as I can't find any relative information.
You could store the mouse position:
Point point = Cursor.Position;
and then use an area with no controls in it to change the focus back to the DirectX9 window,
moving the cursor back to the original position before clicking again? That might work.
The only issue is that the button would still be there so you would need some way of getting it to click to the window rather than the button.
e.g.
Point p = Cursor.Position;
Cursor.Position = new point(x,y);
mouse(leftClick);
Cursor.Position = p;
mouse(leftClick);
the mouse(leftclick) method is here.
The other way to do this would be to track cursor position separately and then on each click, check if the click is within any controls and if so then run that method;
(Please tell me in the comments if there is a way to do this more efficiently as it would actually be quite useful to know)
Here is a solution that is working for me:
It identifies the target program by its title or parts of it and after each Button click it sets the target to be the foreground window again.
I can type into notepad, click a Button and type on..
I start by making the Form click-through.
You may want to do a lot more styling, maybe maximize, remove the control/min/max boxes and the title or even grab the target's position and size and overlay just the target window. This way you can make completely unobtrusive adornments to the target.
(I did that once because the target didn't treat touch screens right; so I made an overlay with holes in its region where the non-touch-defunct controls were. Nobody can even notice that the overlay is there!)
I write a few status info into to the output console..
Instead of the MainWindowTitle you could also use the ProcessName to identify the target. You can also incorporate code to re-establish the process after an error, like the user closing and restarting the target app..
using System.Runtime.InteropServices;
using System.Diagnostics;
...
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
string targetTitle = "Notepad2";
Process theProcess = null;
public Form1()
{
InitializeComponent();
// make the form clickthrough..
TransparencyKey = Color.Fuchsia;
BackColor = TransparencyKey;
// find and keep a reference to the target process
theProcess = findProcess(targetTitle);
// our overlay should stay on top
TopMost = true;
}
Process findProcess(string processTitle)
{
Process[] processList = Process.GetProcesses();
return processList.FirstOrDefault(
pr => pr.MainWindowTitle.ToLower().Contains(targetTitle.ToLower()));
}
void setActive(Process theProcess)
{
if (theProcess == null)
{ Console.WriteLine( "Process " + targetTitle + " not found";) return; }
bool ok = SetForegroundWindow(theProcess.MainWindowHandle);
Console.Write("Process " + theProcess.ProcessName + " (" +
theProcess.MainWindowTitle + + ok ? " OK." : " not OK!" );
}
private void button1_Click(object sender, EventArgs e)
{
// do your stuff..
Console.WriteLine("Testing Button 1");
// done: bring the target process back:
setActive(theProcess);
}
}

I want to display the form before the speech output

My problem is that the program speaks before the form gets displayed.
Here's the load block:
/********************
* *
* Start Game *
* *
********************/
private void Battleship_Load(object sender, EventArgs e)
{
// Interface housekeeping
lblStatus.Font = new Font("HandelGotDLig", 18);
// fill computer board
game.buildBoards();
human = game.Human;
computer = game.Computer;
shot = game.Initialize(ref human, ref shot);
//set up displays
gbComputerHistory.Visible = false;
gbHumanHistory.Visible = false;
gbShot.Visible = false;
lblStatus.Text = "Choose who starts";
gbStart.Visible = true;
// display human board
DisplayBoard(picHuman, human, false);
// display computer ships
DisplayBoard(picComputer, computer, false);
this.Refresh();
#if SPEECH
Say("Welcome to BATTLESHIP! Prepare to Lose!");
Say("Choose who starts first.");
#endif
}
I suspect I could bury it in the paint event, but then I'd have to keep track of whether it has spoken already.
I don't want it speaking every time the form is repainted.
Try adding the speech code to the Form_Shown event. This event is raised whenever the form is shown to the user (when they can actually see it). From MSDN:
The Shown event is only raised the first time a form is displayed; subsequently minimizing, maximizing, restoring, hiding, showing, or invalidating and repainting will not raise this event.

Prevent events from firing multiple times from single action

How can I prevent the firing of multiple events of the same kind triggered by a single action?
For example, I have a ListView containing some items. When I select or deselect all items, the SelectedIndexChanged event is fired once for each item. Rather, I would like to receive a single event indication the user's action (selection/deselection of items), regardless of the number of items.
Is there any way to achieve this?
You can't change the ListView code, and subclassing it doesn't provide many options.
I would suggest that you simply add a small delay (200ms or similar) to your code - i.e. you only do the calculation a little while after the last update. Something like:
using System;
using System.Windows.Forms;
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
ListView list;
TextBox txt;
Timer tmr = new Timer();
tmr.Interval = 200;
Form form = new Form {
Controls = {
(txt = new TextBox { Dock = DockStyle.Fill, Multiline = true}),
(list = new ListView { Dock = DockStyle.Right, View = View.List,
Items = { "abc", "def" , "ghi", "jkl", "mno" , "pqr"}})
}
};
list.SelectedIndexChanged += delegate {
tmr.Stop();
tmr.Start();
};
tmr.Tick += delegate {
tmr.Stop();
txt.Text += "do work on " + list.SelectedItems.Count + " items"
+ Environment.NewLine;
};
Application.Run(form);
}
}
Only by by coming at the problem from a slightly different direction. E.g. subscribe loss of focus.
In the end, the application or runtime cannot raise an event on "all selection changes done" without actually using something else because there is no way for the application to predict whether the user will perform another click on the control while it retains focus.
Even using focus, the user could switch back to that control.
If your ListView is in virtual mode, you could use the
VirtualItemsSelectionRangeChanged event. This event will be fired only once for the user's action (selection/deseclection).

Categories

Resources