I'm trying to implement a theme changer to my app. So far I've done this:
BaseForm - has all the functions to be inherited from, it's a lengthy one so if required I'll post it here. What troubles I'm having is as follows:
MainForm has a button to open UserSettings where the user can change the theme. Problem is, I managed to get it down that the theme applies, but only for the UserSettings window, and I want it to apply to anything that is open (if there is anything else).
Edit: adding more code
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Windows.Forms;
using IniParser;
using ME___Tooling_Designer_App.Properties;
namespace ME___Tooling_Designer_App.Configuration;
public partial class UserSettings : BaseForm
{
public UserSettings()
{
InitializeComponent();
ApplyTheme();
ReadConfiguration();
}
// this is where the theme should be applied upon close
private void CBoxTheme_DropDownClosed(object sender, EventArgs e)
{
var parser = new FileIniDataParser();
var data = parser.ReadFile(_filePathSettings);
if (cBoxTheme.SelectedIndex == 0)
{
data["User_Settings"]["Theme"] = "Dark";
parser.WriteFile(_filePathSettings, data);
GlobalTheme = Enumerators.Theme.Dark;
}
if (cBoxTheme.SelectedIndex == 1)
{
data["User_Settings"]["Theme"] = "Light";
parser.WriteFile(_filePathSettings, data);
GlobalTheme = Enumerators.Theme.Light;
}
ApplyTheme();
}
public override void ApplyThemeSpecific(ThemeSettings settings)
{
panel1.BackColor = settings.PrimaryColor;
label2.ForeColor = settings.PrimaryFontColor;
btnExit.BackColor = settings.PrimaryColor;
btnExit.ForeColor = settings.PrimaryFontColor;
groupBox1.ForeColor = settings.PrimaryFontColor;
groupBox2.ForeColor = settings.PrimaryFontColor;
panel2.BackColor = settings.SecondaryColor;
}
Edit: this is BaseForm code:
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;
namespace ME___Tooling_Designer_App.Configuration;
public class BaseForm : Form
{
public static List<PrivateFontCollection> FontCollections;
public static Enumerators.Theme GlobalTheme { get; set; }
public void ApplyTheme()
{
var settings = new ThemeSettings();
if (GlobalTheme == Enumerators.Theme.Dark)
{
settings.PrimaryColor = Color.FromArgb(32, 30, 50);
settings.SecondaryColor = Color.FromArgb(32, 30, 45);
settings.TertiaryColor = Color.FromArgb(11, 7, 17);
settings.QuaternaryColor = Color.FromArgb(23, 21, 32);
settings.QuinaryColor = Color.FromArgb(35, 32, 39);
settings.SenaryColor = Color.LightGray;
settings.SeptenaryColor = Color.Gainsboro;
settings.PrimaryFontColor = Color.Gainsboro;
}
else if (GlobalTheme == Enumerators.Theme.Light)
{
settings.PrimaryColor = Color.LightGray;
settings.SecondaryColor = Color.GhostWhite;
settings.TertiaryColor = Color.Gainsboro;
settings.QuaternaryColor = Color.DarkGray;
settings.QuinaryColor = Color.Silver;
settings.SenaryColor = Color.Gray;
settings.SeptenaryColor = Color.Black;
settings.PrimaryFontColor = Color.Black;
}
ApplyThemeSpecific(settings);
}
public virtual void ApplyThemeSpecific(ThemeSettings settings)
{
}
}
public class Enumerators
{
public enum Theme
{
NotSet,
Light,
Dark
}
}
public class ThemeSettings
{
public Color PrimaryColor { get; set; }
public Color SecondaryColor { get; set; }
public Color TertiaryColor { get; set; }
public Color QuaternaryColor { get; set; }
public Color QuinaryColor { get; set; }
public Color SenaryColor { get; set; }
public Color SeptenaryColor { get; set; }
public Color PrimaryFontColor { get; set; }
}
this is my Program.cs:
using System;
using System.Windows.Forms;
using IniParser;
using ME___Tooling_Designer_App.Configuration;
using ME___Tooling_Designer_App.Forms;
namespace ME___Tooling_Designer_App
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
var parser = new FileIniDataParser();
var data = parser.ReadFile(Application.StartupPath + #"\" + "Config" + #"\" + "settings.ini");
var themeSetting = data["User_Settings"]["Theme"];
Application.EnableVisualStyles();
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.SetCompatibleTextRenderingDefault(false);
if (themeSetting == "Light")
{
BaseForm.GlobalTheme = Enumerators.Theme.Light;
}
else if (themeSetting == "Dark")
{
BaseForm.GlobalTheme = Enumerators.Theme.Dark;
}
else
{
BaseForm.GlobalTheme = Enumerators.Theme.NotSet;
}
Application.Run(new Login());
}
}
}
tried doing it as MainForm.ApplyTheme, but that just returns to UserSettings nonetheless
Edit: adding snippets of MainForm:
namespace ME___Tooling_Designer_App.Forms;
public partial class MainForm : BaseForm
{
/// <summary>
/// Constructor
/// </summary>
public MainForm()
{
InitializeComponent();
ApplyTheme();
CreateDirectories();
CustomizeDesign();
InitializeDatFiles();
InitializeIniConfiguration();
ReadFont();
ReadComputer();
ReadSystem();
TestTrial();
DaysRemainColoring();
CompareComputerNameAndLicense();
lblUser.Text = Environment.UserName;
}
public override void ApplyThemeSpecific(ThemeSettings settings)
{
panelTopBar.BackColor = settings.PrimaryColor;
btnInfo.BackColor = settings.PrimaryColor;
btnMinimize.BackColor = settings.PrimaryColor;
btnExitTop.BackColor = settings.PrimaryColor;
panelCenter.BackColor = settings.SecondaryColor;
panelSideMenu.BackColor = settings.TertiaryColor;
panelSideMenuLogo.BackColor = settings.TertiaryColor;
btnMainMenu.BackColor = settings.TertiaryColor;
btnSettings.BackColor = settings.TertiaryColor;
btnSave.BackColor = settings.TertiaryColor;
btnLoad.BackColor = settings.TertiaryColor;
btnExit.BackColor = settings.TertiaryColor;
panelQuickView.BackColor = settings.QuaternaryColor;
panelMainMenu.BackColor = settings.QuinaryColor;
panelSettings.BackColor = settings.QuinaryColor;
btnNewProject.BackColor = settings.QuinaryColor;
btnExistingProject.BackColor = settings.QuinaryColor;
btnReadProject.BackColor = settings.QuinaryColor;
btnSearchProject.BackColor = settings.QuinaryColor;
btnUserSettings.BackColor = settings.QuinaryColor;
btnAppSettings.BackColor = settings.QuinaryColor;
}
The solution is to add a timer
Because your problem is to update all windows, you just need to add a timer control to each window.
Timer:
BaseForm:
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApp4 {
public partial class BaseForm : Form {
public BaseForm () {
}
private static Image baseImage=null;
public static Image BaseImage {
get {
return baseImage;
}
set {
baseImage = value;
}
}
}
}
SettingsForm:
using System;
namespace WindowsFormsApp4 {
public partial class SettingsForm : BaseForm {
public SettingsForm () {
InitializeComponent();
}
private void button1_Click (object sender, EventArgs e) {
BaseImage = Resource.tiger;
}
private void button2_Click (object sender, EventArgs e) {
BaseImage = Resource.windows;
}
private void timer1_Tick (object sender, EventArgs e) {
this.BackgroundImage = BaseImage;
}
}
}
Form1:
using System;
namespace WindowsFormsApp4 {
public partial class Form1 : BaseForm {
public Form1 () {
InitializeComponent();
}
private void button1_Click (object sender, EventArgs e) {
SettingsForm settingsForm=new SettingsForm();
settingsForm.ShowDialog();
}
private void timer1_Tick_1 (object sender, EventArgs e) {
this.BackgroundImage = BaseImage;
}
}
}
Resource.resx:
OutPut:
You can change it according to your needs.
If you have any questions, please comment below, and I will continue to follow up.
Try this code on all forms of your project on Activated and Load events.
Such as, MainForm:
private void MainForm_Activated(object sender, EventArgs e)
{
ApplyTheme();
}
Update: Try following line on BaseForm, if it works, add override on all forms and change the color values of all elements:
public virtual void ApplyThemeSpecific(ThemeSettings settings)
{
Background = settings.PrimaryColor; //for example
}
Update 2: Add a timer on all forms that checks theme change, and when theme changes, it changes the visual. Or you can make a event when theme changes.
Related
I am trying to write something along of a "clean" code... I want to make a Pong game, for now based on Forms.
I want to divide the game nicely into classes.
I want to have a ball class, AI that inherits from player class, I want to use the premade Form class for setting main Form properties (width etc).
I made a player class as such and I would like to ask you if the approach for naming, getters and setters and the general idea is correct. Isn't certain bits (if not all of it) rather redundant or badly written, I do not want to base entire "project" on bad assumptions and multiply the same mistakes all over the code.
namespace Pong
{
public class Player
{
protected PictureBox PaddleBox { get; set; }
protected Size PlayerSize
{
get
{
return PlayerSize;
}
set
{
if (PlayerSize.Height > 0 && PlayerSize.Width > 0)
{
PlayerSize = new Size(value.Width, value.Height);
PaddleBox.Size = PlayerSize;
}
}
}
protected Point Location
{
get
{
return Location;
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
protected Color BackColor
{
get
{
return BackColor;
}
set
{
PaddleBox.BackColor = value;
}
}
public Player()
{
PaddleBox = new PictureBox();
}
}
}
FORM class looks something along of this for now, maybe I should pass parameters such as size,location and color in the constructor? What is the best?
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player();
player.PaddleBox.Size = new Size(20, 50);
player.PaddleBox.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2);
player.PaddleBox.BackColor = Color.Blue;
this.Controls.Add(player.PaddleBox);
gameTime = new Timer();
gameTime.Enabled = true;
}
void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
You got an issue here:
protected Point Location
{
get
{
return Location; // <--- this is a circular reference..
// meaning, it will recall this getter again.
}
set
{
PaddleBox.Location = new Point(value.X, value.Y);
}
}
use this instead:
protected Point Location
{
get
{
return PaddleBox.Location;
}
set
{
PaddleBox.Location = value;
}
}
Same with protected Color BackColor
Here is an example, how I would implement it (in your current programming style (powered by notepad))
namespace Pong
{
public partial class Form1 : Form
{
public Timer gameTime;
const int screenWidth = 1248;
const int screenHeight = 720;
public Form1()
{
InitializeComponent();
this.Height= screenHeight;
this.Width=screenWidth;
this.StartPosition=FormStartPosition.CenterScreen;
Player player = new Player(this);
player.PlayerSize = new Size(20, 50);
player.Location = new Point(player.PaddleBox.Width / 2, ClientSize.Height/2-player.PaddleBox.Height/2); // <-- the location is always the upperleft point. don't do this...
player.BackColor = Color.Blue;
gameTime = new Timer();
gameTime.Enabled = true;
}
private void gameTime_Tick(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class Player
{
private PictureBox _paddleBox;
protected Size PlayerSize
{
get
{
return _paddleBox.Size;
}
set
{
if (PlayerSize.Height == 0 || PlayerSize.Width == 0)
throw new ArgumentException("Size must be greater than 0");
_paddleBox.Size = value;
}
}
protected Point Location
{
get { return PaddleBox.Location; }
set { PaddleBox.Location = value; }
}
protected Color BackColor
{
get { return PaddleBox.BackColor; }
set { PaddleBox.BackColor = value; }
}
public Player(Form form)
{
PaddleBox = new PictureBox();
form.Controls.Add(PaddleBox);
}
}
}
You should try to isolate the picturebox in your player class, this will separate the functionality of the form and the picturebox...
I'm developing Pong with MvvmCross. The Paddles Y values change when holding the up and down buttons (buttons in the android view/activity - not keyboard buttons). However this is not being shown in the view (paddles stay in one spot, even though I can see in the console logs that the paddle has gone up or down).
Why are the paddles Y values not properly bound to the view?
Here is the code:
ViewModel:
using System.Linq;
using System.Text;
using System.Threading;
using Cirrious.MvvmCross.ViewModels;
using Pong.Core.ViewModels;
using Pong.Core.Models;
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
: MvxViewModel
{
private string _hello = "Hello MvvmCross";
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
private int _totalFramesBeenHad;
public int TotalFramesBeenHad
{
get { return _totalFramesBeenHad; }
set { _totalFramesBeenHad = value; RaisePropertyChanged(() => TotalFramesBeenHad); }
}
private PlayerPaddle _paddle1;
public int Paddle1
{
get { return _paddle1.Y; }
set { _paddle1.Y = value; RaisePropertyChanged(() => Paddle1); }
}
private ComputerPaddle _paddle2;
public int Paddle2
{
get { return _paddle2.Y; }
set { _paddle2.Y = value; RaisePropertyChanged(() => Paddle2); }
}
protected StandardBall StandardBall;
public GamePlayViewModel()
{
_paddle1 = new PlayerPaddle();
_paddle2 = new ComputerPaddle();
StandardBall = new StandardBall();
}
public void UpdatePaddle1()
{
switch (_paddle1.DetectWallCollision())
{
case "upper":
_paddle1.UpperWallHit();
break;
case "lower":
_paddle1.LowerWallHit();
break;
case "none":
_paddle1.MoveOneFrame();
break;
}
}
public void UpdateBall()
{
if (StandardBall.DetectWallCollision()) StandardBall.HandleWallCollision();
StandardBall.MoveOneFrame();
}
public void SetPaddleDirection(string direction)
{
_paddle1.SetDirection(direction);
}
public void StopPaddle()
{
_paddle1.StopMoving();
}
}
}
child viewmodel (actual one used):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Pong.Core.ViewModels;
using Pong.Core.Models;
using Pong.Droid.Views;
namespace Pong.Droid.ViewModels
{
public class GamePlayViewModelAndroid : GamePlayViewModel
{
public readonly Timer _dispatcherTimer;
public GamePlayView gpv;
public GamePlayViewModelAndroid (GamePlayView gpv)
{
this.gpv = gpv;
TimerCallback timerDelegate = new TimerCallback (Tick);
_dispatcherTimer = new Timer (timerDelegate, null, 0, 1000/Court.FPS);
}
public void Tick(object state)
{
UpdatePaddle1();
gpv.move ();
}
}
}
View:
using Android.App;
using Android.OS;
using Cirrious.MvvmCross.Droid.Views;
using Cirrious;
using Cirrious.CrossCore;
using Cirrious.MvvmCross.Binding;
using Cirrious.MvvmCross.ViewModels;
using Pong.Droid.ViewModels;
using Android.Content.PM;
using Pong.Droid;
using Android.Views;
using Android.Widget;
using Android.Graphics;
using Android.Content;
using Android.Content.Res;
using Cirrious.MvvmCross.Binding.BindingContext;
using Pong.Core.Models;
namespace Pong.Droid.Views
{
[Activity(Label = "!PONG!", ScreenOrientation = ScreenOrientation.Landscape)]
public class GamePlayView : MvxActivity
{
private GamePlayViewModelAndroid _viewModel;
private Button _buttonUp;
private Button _buttonDown;
public GameView GameView;
public LinearLayout ParentLayout;
public LinearLayout ButtonsLayout;
public int _paddle1y;
public int _paddle2y;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
GameView = new GameView (this);
_viewModel = new GamePlayViewModelAndroid(this);
SetUpControls ();
SetUpButtonEvents ();
SetContentView(ParentLayout);
DataContext = _viewModel;
this.ClearAllBindings();
var set = this.CreateBindingSet<GamePlayView, GamePlayViewModelAndroid>();
set.Bind(this).For(v => v._paddle1y).To(vm => vm.Paddle1);
set.Bind(this).For(v => v._paddle2y).To(vm => vm.Paddle2);
set.Apply();
}
void SetUpButtonEvents ()
{
_buttonUp.Touch += (s, e) => {
var handled = false;
if (e.Event.Action == MotionEventActions.Down) {
_viewModel.SetPaddleDirection ("up");
handled = true;
}
else
if (e.Event.Action == MotionEventActions.Up) {
_viewModel.StopPaddle ();
handled = true;
}
e.Handled = handled;
};
_buttonDown.Touch += (s, e) => {
var handled = false;
if (e.Event.Action == MotionEventActions.Down) {
_viewModel.SetPaddleDirection ("down");
handled = true;
}
else
if (e.Event.Action == MotionEventActions.Up) {
_viewModel.StopPaddle ();
handled = true;
}
e.Handled = handled;
};
}
void SetUpControls ()
{
_buttonUp = new Button (this);
_buttonDown = new Button (this);
ParentLayout = new LinearLayout (this);
ButtonsLayout = new LinearLayout (this);
ParentLayout.Orientation = Android.Widget.Orientation.Horizontal;
ButtonsLayout.Orientation = Android.Widget.Orientation.Vertical;
ButtonsLayout.AddView (_buttonUp);
ButtonsLayout.AddView (_buttonDown);
ParentLayout.AddView (ButtonsLayout);
ParentLayout.AddView (GameView);
}
public void move() {
//GameView.paddle1y = _viewModel.Paddle1.Y;
//GameView.paddle2y = _viewModel.Paddle2.Y;
RunOnUiThread (() => GameView.Invalidate ());
}
}
public class GameView : View {
private Bitmap _paddleBmp;
private int _paddle1x;
public int _paddle1y;
private int _paddle2x;
public int _paddle2y;
public GamePlayViewModelAndroid vm;
public GamePlayView View;
public GameView(Context context) : base (context) {
SetPaddleBmp ();
// this.ClearAllBindings();
// var set = this.CreateBindingSet<GameView, GamePlayViewModelAndroid>();
// set.Bind(_paddle1y).To(vm => vm.Paddle1.Y);
// set.Bind(_paddle2y).To(vm => vm.Paddle2.Y);
// set.Apply();
//var set = this.CreateBindingSet<PolicySummaryCell, PolicyComponent<BasePolicy>>();
//set.Bind(_periodOfInsurance).To(vm => vm.PeriodOfInsurance);
//set.Bind(_title).To(vm => vm.Title);
View = (GamePlayView)context;
}
void SetPaddleBmp ()
{
var paddlebmpTemp = BitmapFactory.DecodeResource (Resources, Resource.Drawable.Icon);
_paddleBmp = Bitmap.CreateScaledBitmap (paddlebmpTemp, Paddle.Width, Paddle.Height, false);
}
protected override void OnDraw(Canvas canvas) {
canvas.DrawColor(Color.Aqua);
canvas.DrawBitmap (_paddleBmp, _paddle1x, View._paddle1y, null);
canvas.DrawBitmap (_paddleBmp, _paddle2x, View._paddle2y, null);
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh) {
SetUpCourt (w, h);
}
void SetUpCourt (int w, int h)
{
Court.Width = w;
Court.Height = h;
Court.UpperBound = 0;
Court.LowerBound = h;
Court.LeftBound = 0;
Court.RightBound = w;
ComputerPaddle.X = Court.RightBound - Paddle.Width - 20;
_paddle2x = ComputerPaddle.X;
_paddle1x = PlayerPaddle.X;
}
}
}
Model:
using System.Diagnostics;
namespace Pong.Core.Models
{
public class Paddle
{
public int Y { get; set; }
public int VY { get; set; }
public static readonly int Speed = 600;
public static readonly int Height = 300;
public static readonly int Width = 100;
public void StopMoving()
{
VY = 0;
}
public void SetDirection(string direction)
{
if (direction == "up")
{
VY = -Speed;
}
else if (direction == "down")
{
VY = Speed;
}
}
public string DetectWallCollision()
{
if (Y < Court.UpperBound)
{
return "upper";
}
if (Y > (Court.LowerBound - Paddle.Height))
{
return "lower";
}
return "none";
}
public void UpperWallHit()
{
StopMoving();
Y = Court.UpperBound;
Debug.WriteLine("You hit the top wall");
}
public void LowerWallHit()
{
StopMoving();
Y = Court.LowerBound - Paddle.Height;
Debug.WriteLine("You hit the bottom wall");
}
public void MoveOneFrame()
{
Y += VY/Court.FPS;//this should trigger the RaisePropertyChanged(() => Paddle1)
}
}
public class PlayerPaddle : Paddle {
public static readonly int X = 20;
}
public class ComputerPaddle : Paddle {
public static int X;
}
}
I think your problem here, is that you're updating the Paddle.Y field, but the RaisePropertyChanged() called as your update the Paddle (on set).
See the difference?
Only if you'll set new instance of Paddle1 and Paddle2 property in GamePlayViewModel the setter will be called:
Paddle1 = new Paddle(); //will call the setter of Paddle1 property
Paddle1.Y = 90; //would not call the setter property
What you need to do is to call RaisePropertyChanged when you update the X and Y values of Paddle1 and Paddle2 properties.
The answer to this question is that on the view, to bind a field to the viewModel programmatically, the field must be a property with a getter and setter. Just like this. In the view:
public int _paddle1y { get; set; }
public int _paddle2y { get; set; }
I don't know why this is the case. I think it's a bit of a bug in MvvmCross. But maybe there is a valid reason for it.
Instead of calling _paddle1 directly, call Paddle1 (and 2) so the RaisePropertyChanged event will get called.
I am dynamic creating several classes and labels. I want to update the label text from my class, but I don't have any idea how to implement it.
My class:
public partial class ScannerUtility : INotifyPropertyChanged
{
public System.Timers.Timer timerHeartBeat = new System.Timers.Timer();
public DateTime lastHeartBeat = new DateTime();
public string heartBeatMessage = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public void EnableScannerUtility()
{
timerHeartBeat = new System.Timers.Timer();
timerHeartBeat.AutoReset = true;
timerHeartBeat.Interval = 5000;// 35000;
timerHeartBeat.Elapsed += TimerHeartBeat_Elapsed;
timerHeartBeat.Start();
}
private void TimerHeartBeat_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
TimeSpan timeSinceLastHeartbeat = DateTime.Now.ToUniversalTime() - lastHeartBeat;
if (timeSinceLastHeartbeat > TimeSpan.FromSeconds(30))
{
HeartBeatMessage = "No Answer from Scanner";
}
else
{
HeartBeatMessage = "Scanner OK";
}
}
public string HeartBeatMessage
{
get { return this.heartBeatMessage; }
set
{
if (value != this.heartBeatMessage)
{
this.heartBeatMessage = value;
NotifyPropertyChanged("heartBeatMessage");
}
}
}
}
And the loop where I created it from the main form:
private void CreateSckanners()
{
foreach (BarCodeNodes item in iBarcodeScanners)
{
ScannerUtility util = new ScannerUtility();
util.EnableScannerUtility();
Label lbl = new Label();
lbl.Text = item.IP.ToString();
lbl.Name = item.IP.ToString();
lbl.DataBindings.Add("Text", util, "HeartBeatMessage", false, DataSourceUpdateMode.OnPropertyChanged);
flowLayoutPanel1.Controls.Add(lbl);
flowLayoutPanel1.Update();
}
}
I want the Label to be updated when the timer is elapsed.
I'm affraid that you can not use DataBindigns in your situation. You have to stick whith the InvokeRequired style. Here, SO question about updating controls in another thread.
bokibeg is right. I put the code in case someone is interested.
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 WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
ScannerUtility util = new ScannerUtility(SynchronizationContext.Current);
util.EnableScannerUtility();
util.HeartBeatMessage = "Waiting for scanner...";
this.SuspendLayout();
Label lbl = new Label();
lbl.Text = "Waiting for scanner...";
lbl.Name = "lblTimer";
lbl.Location = new System.Drawing.Point(15, 15);
lbl.AutoSize = true;
lbl.DataBindings.Add("Text", util, "HeartBeatMessage", false, DataSourceUpdateMode.OnPropertyChanged);
this.Controls.Add(lbl);
this.ResumeLayout(true);
}
}
public partial class ScannerUtility : INotifyPropertyChanged
{
private SynchronizationContext _uiThreadContext;
public System.Timers.Timer timerHeartBeat = new System.Timers.Timer();
public DateTime lastHeartBeat = new DateTime();
public string heartBeatMessage = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
public ScannerUtility(SynchronizationContext uiThreadContext)
{
_uiThreadContext = uiThreadContext;
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
_uiThreadContext.Post(onUIPropertyChanged, new PropertyChangedEventArgs(info));
}
;
}
private void onUIPropertyChanged(object state)
{
PropertyChanged(this, (PropertyChangedEventArgs)state);
}
public void EnableScannerUtility()
{
timerHeartBeat = new System.Timers.Timer();
timerHeartBeat.AutoReset = true;
timerHeartBeat.Interval = 5000;// 35000;
timerHeartBeat.Elapsed += TimerHeartBeat_Elapsed;
timerHeartBeat.Start();
}
private void TimerHeartBeat_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
TimeSpan timeSinceLastHeartbeat = DateTime.Now.ToUniversalTime() - lastHeartBeat;
if (timeSinceLastHeartbeat > TimeSpan.FromSeconds(30))
{
HeartBeatMessage = "No Answer from Scanner";
}
else
{
HeartBeatMessage = "Scanner OK";
}
}
public string HeartBeatMessage
{
get
{
return this.heartBeatMessage;
}
set
{
if (value != this.heartBeatMessage)
{
this.heartBeatMessage = value;
NotifyPropertyChanged("HeartBeatMessage");
}
}
}
}
}
I would like to make my own class for setting some values in dialog box in the way that MessageBox.Show() does.
My code is:
MainPage.xaml.cs
using System;
using System.Windows;
using Microsoft.Phone.Controls;
namespace ModalWindow
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
string result = MyModalBox.GiveMeValue();
MessageBox.Show(result);
}
}
}
MyModalBox.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace ModalWindow
{
public class MyModalBox
{
private static Popup modalBox;
public static string GiveMeValue()
{
TextBox textBox = new TextBox();
textBox.Width = 300;
textBox.Height = 100;
Button okButton = new Button();
okButton.Click += okButton_Click;
okButton.Content = "ok";
okButton.Width = 300;
okButton.Height = 100;
StackPanel stack = new StackPanel();
stack.Background = new SolidColorBrush(Colors.Black);
stack.Width = Application.Current.Host.Content.ActualWidth;
stack.Height = Application.Current.Host.Content.ActualHeight;
stack.HorizontalAlignment = HorizontalAlignment.Center;
stack.VerticalAlignment = VerticalAlignment.Center;
stack.Children.Add(textBox);
stack.Children.Add(okButton);
modalBox = new Popup();
modalBox.Child = stack;
modalBox.IsOpen = true;
// how to change my code to return value only after okButton is cklicked?
return textBox.Text;
}
static void okButton_Click(object sender, RoutedEventArgs e)
{
modalBox.IsOpen = false;
}
}
}
Of course it shows no result befor popup appears. How can I change my code to return value onli after clicking button? Is it possible?
Thanks in advance!
You can use TaskComplectionSource for that.
Add this:
public class DialogResult<T>
{
private readonly T _result;
private readonly bool _canceled;
public DialogResult(bool isCanceled)
: this(string.Empty, isCanceled)
{
}
public DialogResult(string result, bool canceled = false)
{
_result = result;
_canceled = canceled;
}
public T GetResults()
{
if (HasDialogBeenCanceled())
throw new InvalidOperationException("Dialog has been canceled - no results");
return _result;
}
public bool HasDialogBeenCanceled()
{
return _canceled;
}
}
// inside your dialog control
private TaskCompletionSource<DialogResult<string>> dialogResultAwaiter;
private Button button;
private TextBlock textBox;
private Popup popup;
public async Task<DialogResult<string>> ShowPopup()
{
dialogResultAwaiter = new TaskCompletionSource<DialogResult<string>>();
button.Tapped += (sender, args) => dialogResultAwaiter.SetResult(new DialogResult<string>(textBox.Text, false));
var popup = new Popup();
popup.Closed += PopupOnClosed;
// popup code
popup.IsOpen = true;
return await dialogResultAwaiter.Task;
}
private void PopupOnClosed(object sender, object o)
{
if (dialogResultAwaiter.Task.IsCompleted)
return;
dialogResultAwaiter.SetResult(new DialogResult<string>(true));
}
In this way you can create your "own await" - which will "end" (and return results) when TaskComplectionSource.SetResult is called.
I am currently trying to build a custom button control for a dice game I am writing in WPF. Everything is working correctly except that the image in the UI while it is running does not change when the the die value has changed. I can confirm through my debug sessions that the image source is changing as expected as well as all values are changing as expected but the image just doesn't seem to want to update. I've tried looking over many similar questions but none of the questions seem to apply to my particular situation it would seem. I am new to WPF and not quite sure what I am doing wrong here... my current code is as follows:
using BluffersDice.GameEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace BluffersDice.Interface.CustomControls
{
public class DieButton : Button
{
private Die _DieValue;
private readonly BitmapImage ONE_IMAGE = new BitmapImage(new Uri("/res/dice/1.png",UriKind.Relative));
private readonly BitmapImage TWO_IMAGE = new BitmapImage(new Uri("/res/dice/2.png", UriKind.Relative));
private readonly BitmapImage THREE_IMAGE = new BitmapImage(new Uri("/res/dice/3.png", UriKind.Relative));
private readonly BitmapImage FOUR_IMAGE = new BitmapImage(new Uri("/res/dice/4.png", UriKind.Relative));
private readonly BitmapImage FIVE_IMAGE = new BitmapImage(new Uri("/res/dice/5.png", UriKind.Relative));
private readonly BitmapImage SIX_IMAGE = new BitmapImage(new Uri("/res/dice/6.png", UriKind.Relative));
private const string HELD_LABEL_TEXT = "Held";
//private bool initcompleted = false;
private Label HoldLabel { get; set; }
public DieButton() : this(new Die())
{}
public DieButton(Die dieValue)
{
Background = Brushes.Transparent;
BorderBrush = new SolidColorBrush(Colors.Transparent);
BorderThickness = new Thickness(6);
HoldLabel = new Label() { MinHeight = 15 };
Click += This_OnClick;
DieValueChanged += DieValueChangedHandler;
dieValue.IsHeldChanged += DieValue_IsHeldChanged;
dieValue.DieValueChanged += DieValueChangedHandler;
_DieValue = dieValue;
Panel = new StackPanel()
{
Orientation = Orientation.Vertical,
Margin = new Thickness(8)
};
DieImage = new Image() { Source = GetDieImageSource() };
Panel.Children.Add(DieImage);
Panel.Children.Add(HoldLabel);
Content = Panel;
UpdateButtonContent();
//initcompleted = true;
}
private void This_OnClick(object sender, RoutedEventArgs e)
{
DieValue.ToggleHold();
}
public event EventHandler DieValueChanged;
public Die DieValue
{
get
{
return _DieValue;
}
set
{
_DieValue = value;
if (DieValueChanged != null)
{
DieValueChanged(this, new EventArgs());
}
}
}
private Image DieImage { get; set; }
private StackPanel Panel { get; set; }
private void DieValue_IsHeldChanged(object sender, EventArgs e)
{
var d = (Die)sender;
if (d.IsHeld)
{
BorderBrush = new SolidColorBrush(Colors.Yellow);
}
else
{
BorderBrush = new SolidColorBrush(Colors.Transparent);
}
HoldLabel.Content = DieValue.IsHeld ? HELD_LABEL_TEXT : string.Empty;
}
private void DieValueChangedHandler(object sender, EventArgs e)
{
DieImage.Source = GetDieImageSource();
UpdateButtonContent();
}
private ImageSource GetDieImageSource()
{
switch (DieValue.Value)
{
case 1:
return ONE_IMAGE;
case 2:
return TWO_IMAGE;
case 3:
return THREE_IMAGE;
case 4:
return FOUR_IMAGE;
case 5:
return FIVE_IMAGE;
case 6:
return SIX_IMAGE;
default:
return null;
}
}
private void UpdateButtonContent()
{
(Panel.Children[0] as Image).Source = GetDieImageSource();
}
}
}
Window Is being used on:
using BluffersDice.GameEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using BluffersDice.Interface.CustomControls;
namespace BluffersDice.Interface
{
/// <summary>
/// Interaction logic for UserTurn.xaml
/// </summary>
public partial class PlayerTurn : Window
{
public Turn TurnState { get; set; }
private Roll CurrentRoll { get; set; }
public PlayerTurn()
{
CurrentRoll = new Roll();
InitializeComponent();
btn_Die1 = new DieButton(CurrentRoll.Dice[0]);
btn_Die2 = new DieButton(CurrentRoll.Dice[1]);
btn_Die3 = new DieButton(CurrentRoll.Dice[2]);
btn_Die4 = new DieButton(CurrentRoll.Dice[3]);
btn_Die5 = new DieButton(CurrentRoll.Dice[4]);
GameState.Caller.StartNewTurn();
TurnState = GameState.Caller.StartNewTurn();
lbl_PlayerTitle.Text = string.Format(lbl_PlayerTitle.Text, GameState.Caller.Id);
}
private void btn_DieValuechanged(object sender, EventArgs ea)
{
var d = (Die)sender;
MessageBox.Show(String.Format("Die Button {0} Value Changed To {1}", d.Id, d.Value));
}
private void DieValueChanged(object sender, EventArgs e)
{
var d = (Die)sender;
//MessageBox.Show(String.Format("Die {0} Value Changed To {1}", d.Id, d.Value));
}
private void RollDice_btnClick(object sender, RoutedEventArgs e)
{
CurrentRoll.RollDice();
}
}
}
Each time you want to change your image try doing the below
= new BitmapImage(new Uri(...
I have read around that usually works or you can follow the solution discussed here
http://social.msdn.microsoft.com/Forums/en/wpf/thread/976e8d89-aafd-4708-9e4f-87655a5da558