change child value from parent on timer elapse - c#

I have a simple parent-child component :
parent :
<div class="container-fluid">
<div>
<ChildComponent RandomNumber="#RandomNumberX" ></ChildComponent>
</div>
</div>
#code {
public double RandomNumberX = 0.1;
private Timer timer = new Timer();
protected override void OnInitialized()
{
timer.Interval = 3000;
timer.Elapsed -= Set;
timer.Elapsed += Set;
timer.Start();
}
private void Set(object sender, ElapsedEventArgs e)
{
var rng = new Random();
var x = rng.Next(500, 600);
RandomNumberX = x;
}}
Child:
<svg height="100" width="100" style="position:absolute; border-color: black; ">
<g>
<circle cx="50%" cy="25" r="5" stroke="red" stroke-width="2" fill="red" />
<text x="50%" y="50" text-anchor="middle">#RandomNumber</text>
</g>
</svg>
#code {
[Parameter]
public double RandomNumber { get; set; }
protected override void OnParametersSet()
{
}
}
when running app "RandomNumber" show 0.1 and does not change while in parent component it changes on timer elapsed.

I animated a SVG clock a little while back that does this:
Note: The use of dispose and how I reduce the calculations to only when needed by checking for a second change.
Clock.razor
<div class="d-flex flex-column align-items-center">
<div class="time-sm">#timeZone.Id</div>
<svg width="#Size" height="#Size" viewBox="0 0 1000 1000">
<circle cx="#center" cy="#center" r="#radius" fill="#FaceColor" />
<ClockHand Angle="hourAngle" Color="#HourColor" Width="50" Length="0.9" />
<ClockHand Angle="minuteAngle" Color="#MinuteColor" Width="30" Length="0.95" />
<ClockHand Angle="secondAngle" Color="#SecondColor" Width="20" Length="1" />
</svg>
<div class="time-sm">#currentSecond.DateTime.ToShortTimeString()</div>
<div class="time-sm">#currentSecond.DateTime.ToString("dddd")</div>
</div>
Clock.razor.cs
public partial class Clock : ComponentBase, IDisposable
{
internal const int radius = 500;
internal const int size = 1000;
internal const int center = size / 2;
private double secondAngle = 0;
private double minuteAngle = 0;
private double hourAngle = 0;
private TimeZoneInfo timeZone;
private DateTimeOffset currentSecond;
private readonly System.Timers.Timer timer = new();
[Parameter]
public int Size { get; set; } = 50;
[Parameter]
public string FaceColor { get; set; } = "#f5f5f5";
[Parameter]
public string HourColor { get; set; } = "blue";
[Parameter]
public string MinuteColor { get; set; } = "green";
[Parameter]
public string SecondColor { get; set; } = "red";
[Parameter]
public string TimeZone { get; set; }
protected override void OnInitialized()
{
if (string.IsNullOrWhiteSpace(TimeZone) is true)
{
timeZone = TimeZoneInfo.Local;
}
else
{
timeZone = TimeZoneInfo.FindSystemTimeZoneById(TimeZone);
}
timer.Interval = 100;
timer.Elapsed += Timer_Elapsed;
UpdateClock();
timer.Start();
}
public static DateTime GmtToPacific(DateTime dateTime)
{
return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
UpdateClock();
InvokeAsync(StateHasChanged);
}
private void UpdateClock()
{
const double radiansPer60 = 360 / 60 * Math.PI / 180;
const double radiansPer12 = 360 / 12 * Math.PI / 180;
var currentTime = TimeZoneInfo.ConvertTime(DateTimeOffset.Now, timeZone);
var roundedSencond = new DateTimeOffset(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Second, default);
if (roundedSencond != currentSecond)
{
currentSecond = roundedSencond;
var seconds = currentTime.Second;
var minutes = currentTime.Minute;
var hours = currentTime.Hour % 12;
secondAngle = seconds * radiansPer60;
minuteAngle = minutes * radiansPer60 + secondAngle / 60;
hourAngle = hours * radiansPer12 + minuteAngle / 12;
}
}
public void Dispose()
{
if (timer is not null)
{
timer.Dispose();
}
}
}
ClockHand.razor
<line x1="500" y1="500" x2="#X" y2="#Y" style="stroke:#Color;stroke-width:#Width" />
ClockHand.razor.cs
public partial class ClockHand : ComponentBase
{
[Parameter]
public double Angle { get; set; }
[Parameter]
public double Length { get; set; } = 1;
[Parameter]
public string Color { get; set; } = "black";
[Parameter]
public int Width { get; set; }
double X => Math.Sin(Angle) * Clock.radius * Length + Clock.center;
double Y => Math.Cos(Angle) * -Clock.radius * Length + Clock.center;
}
Useage
<Clock TimeZone="Australia/Sydney" />
or
<Clock />

TimerElapsed is not a 'normal' Blazor lifecycle event. So it won't automatically trigger a re-render. (A ButtonClick event would, for example).
So you need to call StatehasChanged, and to cater for the possibility it's on another Thread you need to InvokeAsync that.
private async void Set(object sender, ElapsedEventArgs e)
{
var rng = new Random();
var x = rng.Next(500, 600);
RandomNumberX = x;
await InvokeAsync(StatehasChnaged);
}
You should normally avoid async void but this is exactly the suituation it was created for.
Side note: timer.Elapsed -= Set; during Initialization is only half a solution.
Use #implements IDisposable on the top of your page and add
public void Dispose()
{
timer.Elapsed -= Set;
}

Related

C# Blazor WebAssembly: Argument 2: cannot convert from 'void' to 'Microsoft.AspNetCore.Components.EventCallback'

I'm new to blazor C# and trying to make a simple countdown timer website. My website consist of:
Text to display the timer
Start and stop button
Buttons to set the timer
I'm having a problem in the buttons to set the timer. When i click on it, it won't set the timer display and i got an error Argument 2: cannot convert from 'void' to 'Microsoft.AspNetCore.Components.EventCallback'. I search on Youtube for the EventCallback topic but the problem is, my component is not seperated while in the video the example code got seperated components linked together. Here's the code.
Index.razor
#page "/"
#inherits FrontEnd.Components.Timer
<h1>Timer</h1>
<p class="timer">#Hours.ToString("00"):#Minutes.ToString("00"):#Seconds.ToString("00")</p>
#foreach (var timer in timerCollections)
{
// Here's the problem
<button #onclick="SetTimer(timer.Id)">#timer.Hours.ToString("00"):#timer.Minutes.ToString("00"):#timer.Seconds.ToString("00")</button>
}
<br/>
<button #onclick="StartTimer" disabled=#StartButtonIsDisabled>Start</button>
<button #onclick="StopTimer" disabled=#StopButtonIsDisabled>Stop</button>
Timer.cs
using System;
using System.Timers;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
namespace FrontEnd.Components
{
public class TimerStructure
{
public int Id { get; set; }
public int Hours { get; set; }
public int Minutes { get; set; }
public int Seconds { get; set; }
}
public class Timer : ComponentBase
{
public List<TimerStructure> timerCollections = new List<TimerStructure>(){
new TimerStructure(){ Id = 1, Hours = 0, Minutes = 30, Seconds = 0 },
new TimerStructure(){ Id = 2, Hours = 1, Minutes = 0, Seconds = 0 }
};
public int Index { get; private set; }
public int Hours { get; set; } = 0;
public int Minutes { get; set; } = 0;
public int Seconds { get; set; } = 0;
public bool StopButtonIsDisabled { get; set; } = true;
public bool StartButtonIsDisabled { get; set; } = false;
private static System.Timers.Timer aTimer;
// and this is the function related to the problem
public void SetTimer(int value)
{
this.Index = value - 1;
Hours = timerCollections[Index].Hours;
Minutes = timerCollections[Index].Minutes;
Seconds = timerCollections[Index].Seconds;
}
public void StopTimer()
{
aTimer.Stop();
aTimer.Dispose();
StopButtonIsDisabled = true;
StartButtonIsDisabled = false;
Console.WriteLine($"{Hours}:{Minutes}:{Seconds}");
}
public void StartTimer()
{
aTimer = new System.Timers.Timer(1000);
aTimer.Elapsed += CountDownTimer;
aTimer.Start();
StopButtonIsDisabled = false;
StartButtonIsDisabled = true;
}
public void CountDownTimer(Object source, ElapsedEventArgs e)
{
if(Seconds == 0 && Minutes > 0)
{
Minutes -= 1;
Seconds = 59;
} else if (Minutes == 0 && Seconds == 0 && Hours > 0)
{
Hours -= 1;
Minutes = 59;
Seconds = 59;
} else if (Hours == 0 && Minutes == 0 && Seconds == 0)
{
aTimer.Stop();
aTimer.Dispose();
StopButtonIsDisabled = true;
StartButtonIsDisabled = false;
} else
{
Seconds -= 1;
}
InvokeAsync(StateHasChanged);
}
}
}
Try: <button #onclick="() => SetTimer(timer.Id)">

C# Change a created objects value from another class

I have a Main.class which runs a timer at 5 seconds (when it resets i want to do a bunch of methods and then repeat the timer). I have a Water.class with a currentReserve-property and a pop.class with a CurrentThirst and MaximumThirst-propertys. In the Pop class i have a method thats get called when the timer reset. Basically i want the popclass to consume the currentReserve-int in the object which i have created in the Mainclass. I provide the essential code and mark with comments where the debugger thinks iam wrong:
public AITest()
{
public Water _water;
public Pop _pop;
Timer dayTimer = new Timer();
int timeLeft = 5;
public AITest()
{
InitializeComponent();
_water = new Water(8000);
lblWater.Text = _water.CurrentReserve.ToString();
_pop = new Pop(100, 100);
dayTimer.Interval = 1000;
dayTimer.Enabled = true;
dayTimer.Tick += dayTimer_Tick;
dayTimer.Start();
lblCountDown.Text = timeLeft.ToString();
}
private void dayTimer_Tick(object sender, EventArgs e)
{
lblCountDown.Text = timeLeft.ToString();
timeLeft -= 1;
if (timeLeft < 0)
{
timeLeft = 5;
_water.CurrentReserve += 100;
_pop.HaveToDrink();
lblWater.Text = _water.CurrentReserve.ToString();
}
}
}
}
public class Pop
{
public int CurrentThirst { get; set; }
public int MaximumThirst { get; set; }
public Water _water;
public Pop(int currentThirst, int maximumThirst)
{
CurrentThirst = currentThirst;
MaximumThirst = maximumThirst;
}
public void HaveToDrink()
{
CurrentThirst -= 30;
_water.CurrentReserve -= 30; //Here i get an error
}
}
public class Water
{
public int CurrentReserve { get; set; }
public Water(int currentReserve)
{
CurrentReserve = currentReserve;
}
}
You've got a _water member twice: once in AITest and once in the Pop class. Use only one, and pass it around. The exception you get will be a NullReferenceException, because nothing is ever assigned to the Pop._water member.

Unity saving via playerprefs bugged?

Sorry, I that I included all the code but I really can't find the error: I play and save and after I buy something with the BuyTaxes or Buymarket and quit and stop it and play it again it shows all the UI shows and all the prices for the market and the discount use are set to 0. But I cant figure out why.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameScript : MonoBehaviour
{
public Text GoldText;
public Text ArmyPowerText;
public Text PlayerNametext;
public Text Button1Text;
public Text Button2Text;
public int gold;
public int armypower;
public string PlayerName;
public int Level;
public int Income;
public int incomeboost;
public int discount;
public float lvlremainder;
public GameObject ButtonHolder;
public Slider LevelBar;
public float LevelProgress;
private float FillSpeed = 0.5f;
public GameObject UIHolder;
public GameObject StartForm;
public InputField NameInput;
public GameObject ArmyUI;
public GameObject InvestUI;
public GameObject LaboratoryUI;
private float time = 0.0f;
public float IncomePerdiod = 5f;
public bool GameStart = true;
//Invest Prices
//Market
public int marketprice;
public int marketlevel;
public Text marketpricetext;
public Text markettext;
//Taxes
public int taxesprice;
public int taxeslevel;
public Text taxespricetext;
public Text taxestext;
void Start()
{
Load();
if(GameStart == true)
{
gold = 100;
armypower = 0;
Level = 1;
LevelProgress = 0;
//Laboratory
discount = 1;
Income = 1;
incomeboost = 1;
//Invest
marketlevel = 0;
taxeslevel = 0;
ArmyUIHide();
InvestUIHide();
ButtonHide();
UIHide();
StartForm.SetActive(true);
Save();
}
if (GameStart == false)
{
StartForm.SetActive(false);
ArmyUIHide();
InvestUIHide();
ButtonShow();
UIShow();
Save();
}
}
void Update()
{
if(time >= IncomePerdiod)
{
time = 0.0f;
gold += Income * incomeboost;
}
time += Time.deltaTime;
Save();
GoldText.text = "Gold: " + gold;
ArmyPowerText.text = "Army Power: " + armypower;
PlayerNametext.text = PlayerName + " LVL " + Level;
//Market
markettext.text = "Market LVL " + marketlevel;
marketpricetext.text = marketprice.ToString();
marketprice = 50 * discount;
//Taxes
taxestext.text = "Taxes LVL " + taxeslevel;
taxespricetext.text = taxesprice.ToString();
taxesprice = 250 * discount;
if (LevelBar.value < LevelProgress)
{
LevelBar.value += FillSpeed * Time.deltaTime;
}
if (LevelBar.value > LevelProgress)
{
LevelBar.value = LevelProgress;
}
if (LevelProgress >= 1)
{
Level++;
LevelProgress = 0;
}
}
public void Save()
{
//UI
PlayerPrefs.SetString("gold", gold.ToString());
PlayerPrefs.SetString("armypower", armypower.ToString());
PlayerPrefs.SetString("GameStart", GameStart.ToString());
PlayerPrefs.SetString("PlayerName", PlayerName.ToString());
PlayerPrefs.SetString("Level", Level.ToString());
PlayerPrefs.SetString("LevelProgress", LevelProgress.ToString());
//Laboratory
PlayerPrefs.SetString("discount", discount.ToString());
PlayerPrefs.SetString("Income", Income.ToString());
PlayerPrefs.SetString("incomeboost", incomeboost.ToString());
//Invest
PlayerPrefs.SetString("marketlevel", marketlevel.ToString());
PlayerPrefs.SetString("taxeslevel", taxeslevel.ToString());
}
public void Load()
{
//UI
gold = int.Parse(PlayerPrefs.GetString("gold", "100"));
armypower = int.Parse(PlayerPrefs.GetString("armypower", "0"));
GameStart = bool.Parse(PlayerPrefs.GetString("GameStart", "true"));
PlayerName = PlayerPrefs.GetString("PlayerName", "Guest");
Level = int.Parse(PlayerPrefs.GetString("Level", "1"));
LevelProgress = int.Parse(PlayerPrefs.GetString("LevelProgress", "0"));
//Laboratory
discount = int.Parse(PlayerPrefs.GetString("discount", "1"));
Income = int.Parse(PlayerPrefs.GetString("Income", "1"));
incomeboost = int.Parse(PlayerPrefs.GetString("incomeboost", "1"));
//Invest
marketlevel = int.Parse(PlayerPrefs.GetString("marketlevel", "50"));
taxeslevel = int.Parse(PlayerPrefs.GetString("taxeslevel", "250"));
}
public void ButtonHide()
{
ButtonHolder.SetActive(false);
}
public void ButtonShow()
{
ButtonHolder.SetActive(true);
}
public void UIHide()
{
UIHolder.SetActive(false);
}
public void UIShow()
{
UIHolder.SetActive(true);
}
public void ArmyUIShow()
{
ArmyUI.SetActive(true);
}
public void ArmyUIHide()
{
ArmyUI.SetActive(false);
}
public void LaboratoryUIShow()
{
LaboratoryUI.SetActive(true);
}
public void LaboratoryUIHide()
{
LaboratoryUI.SetActive(false);
}
public void InvestUIShow()
{
InvestUI.SetActive(true);
}
public void InvestUIHide()
{
InvestUI.SetActive(false);
}
public void EnterName()
{
PlayerName = NameInput.text;
StartForm.SetActive(false);
UIShow();
GameStart = false;
ButtonShow();
}
public void ArmyClick()
{
ArmyUIShow();
ButtonHide();
LaboratoryUIHide();
InvestUIHide();
}
public void InvestClick()
{
InvestUIShow();
ButtonHide();
LaboratoryUIHide();
ArmyUIHide();
}
public void LaboratoryClick()
{
ArmyUIHide();
ButtonHide();
LaboratoryUIShow();
InvestUIHide();
}
public void Home()
{
ArmyUIHide();
InvestUIHide();
ButtonShow();
LaboratoryUIHide();
}
//Buy Invest
public void BuyMarket()
{
if(gold >= marketprice && marketlevel < 5)
{
marketlevel++;
Income += 1;
gold -= marketprice;
if (LevelProgress < 1f - 0.05f)
{
LevelProgress += 0.15f;
}
else
{
lvlremainder += (LevelProgress + 0.05f - 1f);
Level++;
LevelProgress = 0;
LevelProgress += lvlremainder;
}
}
}
public void BuyTaxes()
{
if (gold >= taxesprice && taxeslevel < 10)
{
taxeslevel++;
Income += 3;
gold -= taxesprice;
if (LevelProgress < 1f - 0.15f)
{
LevelProgress += 0.15f;
}
else
{
lvlremainder += (LevelProgress + 0.15f - 1f);
Level++;
LevelProgress = 0;
LevelProgress += lvlremainder;
}
}
}
The saving system in your code is not developed correctly, remove the Save() method call from the Start and Update methods, and call it only when the necessary event occurs, such as the end of the game.
Do local prop like this to save data:
private int Gold
{
get => PlayerPrefs.GetInt("gold");
set => PlayerPrefs.SetInt("gold", value);
}
I found the fault I parsed as int for a float that messed the whole saving system up!

Blazor get div position / coordinates

I'm creating a popup component and I want this to be movable. I can move it using the top / left style, but for now they are init to top:0;left:0; and so the popup appear on the top left corner of the page. I'm looking to make it appear on the center of the page and then get the Top Left coordinates of my div in ordor to manage properly my calcul after.
here is what I have now:
<div class="child-window" draggable="true" style="position:absolute; top: #(offsetY)px; left: #(offsetX)px; border-color: black;" #ondragend="OnDragEnd" #ondragstart="OnDragStart">
<div class="cw-content">
#Content
</div>
</div>
#code {
private double startX, startY, offsetX, offsetY;
protected override void OnInitialized() {
base.OnInitialized();
ResetStartPosition();
}
private void ResetStartPosition() {
//Set offsetX & offsetY to the top left div position
}
private void OnDragStart(DragEventArgs args) {
startX = args.ClientX;
startY = args.ClientY;
}
private void OnDragEnd(DragEventArgs args) {
offsetX += args.ClientX - startX;
offsetY += args.ClientY - startY;
}
}
At the moment it is possible with JS only
public class BoundingClientRect
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public double Top { get; set; }
public double Right { get; set; }
public double Bottom { get; set; }
public double Left { get; set; }
}
private async Task OnElementClick(MouseEventArgs e)
{
var result = await JSRuntime.InvokeAsync<BoundingClientRect>("MyDOMGetBoundingClientRect", MyElementReference);
var x = (int) (e.ClientX - result.Left);
var y = (int) (e.ClientY - result.Top);
// now you can work with the position relative to the element.
}
and
<script> MyDOMGetBoundingClientRect = (element, parm) => { return element.getBoundingClientRect(); }; </script>

Take the sum of a list as variable and edit it at runtime

I'm trying to calculate the end value of a specific variable that depends whether a checkbox is checked. This is however dynamically done.
What I'm trying to achieve:
The example above works fine but it starts going wrong when I start to uncheck the check boxes. It subtracts by the double amount of the time and I have no idea why:
My Code (TimeSpent is the variable that I want to have at the end):
Class
public class Activity
{
public int ID { get; set; }
public int IncidentID { get; set; }
public string Description { get; set; }
public int TimeSpent { get; set; }
public static int StartX { get; set; } = 10;
public static int StartY { get; set; } = 10;
private TextBox textBox = null;
private CheckBox checkBox = null;
public Activity(int iD, int incidentID, string description, int timeSpent)
{
this.ID = iD;
this.IncidentID = incidentID;
this.Description = description;
this.TimeSpent = timeSpent;
}
public Activity()
{ }
public bool isChecked() {
return checkBox.Checked;
}
public void setTimeSpent(int timeSpent) {
this.TimeSpent = timeSpent;
textBox.Text = timeSpent.ToString();
}
public int getTimeSpent()
{
return Int32.Parse(textBox.Text);
}
public void DrawToForm(Panel p)
{
var label = new Label();
textBox = new TextBox();
checkBox = new CheckBox();
checkBox.Checked = true;
textBox.Size = new System.Drawing.Size(40, 20);
label.Text = Description.ToString();
label.AutoSize = true;
textBox.Text = TimeSpent.ToString();
label.Left = StartX;
label.Top = StartY;
StartX += 430;// Move position to right
textBox.Left = StartX;
textBox.Top = StartY;
StartX += 160;// Move position to right
checkBox.Left = StartX;
checkBox.Top = StartY;
StartX = 10;// Reset to start
StartY += 50;// Move position to down
p.Controls.Add(label);
p.Controls.Add(textBox);
p.Controls.Add(checkBox);
}
}
GUI:
private void Btn_Validate_Click(object sender, EventArgs e)
{
tb_TotalTime.Text = calculateTotalTime().ToString();
}
public int calculateTotalTime()
{
int total = 0;
foreach (Activity a in activities)
{
if(a.isChecked())
{
total += a.getTimeSpent();
}else
{
total -= a.getTimeSpent();
}
}
return total;
}
Why is subtracting not done correctly?
You subtract when the checkbox is not checked:
if(a.isChecked())
{
total += a.getTimeSpent();
}
else
{
total -= a.getTimeSpent();// << THIS
}
So with the setup of your 2nd image (top 2 unchecked bottom one checked) you do this:
0 - 5 - 5 + 5 = -5
The 0 comes from the default value of Total (int)
To fix this you could just remove the else clause as you don't need to subtract billable time ever.
You should remove subtracting at all.
So change it to :
if(a.isChecked())
{
total += a.getTimeSpent();
}
You have total=0, you have not added all values before calculating. So you don't need subtracting.
Your logic is add if Check subtract if unchecked. Your logic should be add if Checked only.
private void Btn_Validate_Click(object sender, EventArgs e)
{
tb_TotalTime.Text = calculateTotalTime().ToString();
}
public int calculateTotalTime()
{
int total = 0;
foreach (Activity a in activities)
{
if (a.isChecked())
{
total += a.getTimeSpent();
}
}
return total;
}

Categories

Resources