Update UI from dynamic created class - c#

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");
}
}
}
}
}

Related

C# ApplyTheme method to work on all forms simultaneously

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.

Reading an Xbox controller in a WPF application

Is it possible to read an Xbox One controller in a WPF application?
I'm connecting it through a USB cable. I'd like to get boolean values from buttons and be able to read analog values from sticks and triggers. I will be using those values to control a Pololu 3pi robot.
Is there a simple way to achieve that?
Try this. Code4Fun might help you with your controller.
[#CODING4FUN] #XboxOne Game Controller + C# = fun time!
From the website:
The main view of the WPF application code is the following.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Threading;
using SharpDX.XInput;
namespace ElBruno.GameController
{
public partial class MainWindow : INotifyPropertyChanged
{
DispatcherTimer _timer = new DispatcherTimer();
private string _leftAxis;
private string _rightAxis;
private string _buttons;
private Controller _controller;
public MainWindow()
{
DataContext = this;
Loaded += MainWindow_Loaded;
Closing += MainWindow_Closing;
InitializeComponent();
_timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(100)};
_timer.Tick += _timer_Tick;
_timer.Start();
}
void _timer_Tick(object sender, EventArgs e)
{
DisplayControllerInformation();
}
void DisplayControllerInformation()
{
var state = _controller.GetState();
LeftAxis = string.Format("X: {0} Y: {1}", state.Gamepad.LeftThumbX, state.Gamepad.LeftThumbY);
RightAxis = string.Format("X: {0} Y: {1}", state.Gamepad.RightThumbX, state.Gamepad.RightThumbX);
//Buttons = string.Format("A: {0} B: {1} X: {2} Y: {3}", state.Gamepad.Buttons.ToString(), state.Gamepad.LeftThumbY);
Buttons = string.Format("{0}", state.Gamepad.Buttons);
}
void MainWindow_Closing(object sender, CancelEventArgs e)
{
_controller = null;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_controller = new Controller(UserIndex.One);
if (_controller.IsConnected) return;
MessageBox.Show("Game Controller is not connected ... you know ;)");
App.Current.Shutdown();
}
#region Properties
public string LeftAxis
{
get
{
return _leftAxis;
}
set
{
if (value == _leftAxis) return;
_leftAxis = value;
OnPropertyChanged();
}
}
public string RightAxis
{
get
{
return _rightAxis;
}
set
{
if (value == _rightAxis) return;
_rightAxis = value;
OnPropertyChanged();
}
}
public string Buttons
{
get
{
return _buttons;
}
set
{
if (value == _buttons) return;
_buttons = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

Background Worker Call Method or something

I have a method/procedure which works well, however it takes ages to do its thing so I want to move it into a background worker so people can still use the app.
Here is the code. (I cut down as much as I could)
public partial class NetworkInformation : UserControl, INotifyPropertyChanged
{
public NetworkInformation()
{
InitializeComponent();
Discovery();
}
public void Discovery()
{
GetIcon Icon = new GetIcon();
BitmapImage IconOfComputer = null;
List<DiscoveredComputer> NetworkedComputers = new List<DiscoveredComputer>();
DirectoryEntry Discover = new DirectoryEntry("WinNT://Workgroup");
BitmapImage On = Icon.LoadIcon(#"/Images/Icons/ComputerOn.ico");
BitmapImage Off = Icon.LoadIcon(#"/Images/Icons/ComputerOff.ico");
foreach (DirectoryEntry Node in Discover.Children)
{
try
{
if (Node.Properties.Count > 0)
{
IconOfComputer = On;
}
}
catch
{
IconOfComputer = Off;
}
if (Node.Name != "Schema") { NetworkedComputers.Add(new DiscoveredComputer { Image = IconOfComputer, ComputerName = Node.Name, MyToolTip = "Node Type = " + Node.SchemaEntry.Name }); }
}
ListView_LocalComputers.ItemsSource = NetworkedComputers;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class DiscoveredComputer : INotifyPropertyChanged
{
private string _ComputerName;
public string ComputerName
{
get { return _ComputerName; }
set
{
_ComputerName = value;
this.NotifyPropertyChanged("ComputerName");
}
}
private BitmapImage _Image;
public BitmapImage Image {
get { return _Image; }
set
{
_Image = value;
this.NotifyPropertyChanged("Image");
}
}
private String _MyToolTip;
public String MyToolTip
{
get { return _MyToolTip; }
set
{
_MyToolTip = value;
this.NotifyPropertyChanged("ToolTip");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class GetIcon
{
public BitmapImage IconStorage { get; set; }
public BitmapImage LoadIcon(String IconPath)
{
BitmapImage GeneratedIcon = new BitmapImage();
GeneratedIcon.BeginInit();
GeneratedIcon.UriSource = new Uri("pack://application:,,," + IconPath, UriKind.RelativeOrAbsolute);
GeneratedIcon.EndInit();
IconStorage = GeneratedIcon;
return GeneratedIcon;
}
}
}
This all works awesomely, somehow...
Here is the code I:developed for my background worker
public partial class MyBackgroundWorker : UserControl
{
WorkerData BGW;
public MyBackgroundWorker()
{
InitializeComponent();
BGW = new WorkerData();
#region Workers Events
BGW.ThisWorker.DoWork += new DoWorkEventHandler(Workers_DoWork);
BGW.ThisWorker.ProgressChanged += new ProgressChangedEventHandler(Workers_Progress);
BGW.ThisWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Workers_Completed);
BGW.ThisWorker.WorkerReportsProgress = true;
BGW.ThisWorker.WorkerSupportsCancellation = true;
#endregion
}
public void RibbonButton_EventClickStart(object sender, RoutedEventArgs e)
{
BGW.ThisWorker.RunWorkerAsync();
}
public void UserForm_Loaded(object sender, RoutedEventArgs e)
{
}
public void RibbonButton_EventClick(object sender, RoutedEventArgs e)
{
BGW.ThisWorker.CancelAsync();
}
public void Workers_DoWork(object sender, DoWorkEventArgs e)
{
}
public void Workers_Progress(object sender, ProgressChangedEventArgs e)
{
BGW.ThisWorkersProgress = e.ProgressPercentage;
}
public void Workers_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) { BGW.ThisWorkersResult = "Cancelled By User"; }
else if (e.Error != null) { BGW.ThisWorkersResult = "Error Encountered: " + e.Error.Message; }
else
{
BGW.ThisWorkersResult = "Task Completed Successfully";
BGW.WorkersReturnObject = e.Result;
}
}
}
public class WorkerData
{
public BackgroundWorker ThisWorker { get; set; }
public int ThisWorkersProgress { get; set; }
public string ThisWorkersResult { get; set; }
public object WorkersReturnObject { get; set; }
public object ThisWorkersJob { get; set; }
public WorkerData()
{
ThisWorker = new BackgroundWorker();
}
}
So how do I get my background worker to run the Discovery method I have created?
You need to do your work in the DoWork event handler.
I don't know if you need a whole separate class for this. I prefer to create these as I need them, on the fly. I think you'll get yourself shoehorned, where you'll use your class in multiple places and then decide you want to do something else in Workers_Completed in certain cases, or do something different when an error occurs in certain cases, and that one class could end up being a tangled-up pain. That's just my opinion though.
Also, you have to be very careful about touching the UI thread from your BackgroundWorker. In the example below, I'm passing in your node count to the DoWork event, instead of having it possibly touch a UI component directly. I'm also passing the list to the RunWorkerCompleted event, so that you're back in the main thread when it tries to attach the list to your ListView.
var bw = new BackgroundWorker();
bw.DoWork += (s, e) =>
{
var nodePropertiesCount = (int)e.Argument;
// the guts of `Discovery` go in here
e.Result = NetworkedComputers;
};
bw.RunWorkerCompleted += (s, e) =>
{
if (e.Error != null)
{
// Task Completed Successfully
ListView_LocalComputers = (List<DiscoveredComputer>)e.Result;
}
else
{
// Error Encountered
}
};
bw.RunWorkerAsync(Node.Properties.Count);
SLaks answer is correct, but you apparently don't understand what that means. I'd suggest taking the guts of Discover() and putting them in the Workers_DoWork() method like this:
public void Workers_DoWork(object sender, DoWorkEventArgs e)
{
var backgroundWorker = sender as BackgroundWorker;
GetIcon Icon = new GetIcon();
BitmapImage IconOfComputer = null;
List<DiscoveredComputer> NetworkedComputers = new List<DiscoveredComputer>();
DirectoryEntry Discover = new DirectoryEntry("WinNT://Workgroup");
BitmapImage On = Icon.LoadIcon(#"/Images/Icons/ComputerOn.ico");
BitmapImage Off = Icon.LoadIcon(#"/Images/Icons/ComputerOff.ico");
while (!backgroundWorker.CancellationPending)
{
foreach (DirectoryEntry Node in Discover.Children)
{
try
{
if (Node.Properties.Count > 0)
{
IconOfComputer = On;
}
}
catch
{
IconOfComputer = Off;
}
if (Node.Name != "Schema") { NetworkedComputers.Add(new DiscoveredComputer { Image = IconOfComputer, ComputerName = Node.Name, MyToolTip = "Node Type = " + Node.SchemaEntry.Name }); }
}
break;
}
if(backgroundWorker.CancellationPending)
{
e.Cancel = true;
}
else
{
e.Result = NetworkedComputers;
}
}
And then modifying your Workers_Completed() like this:
public void Workers_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) { BGW.ThisWorkersResult = "Cancelled By User"; }
else if (e.Error != null) { BGW.ThisWorkersResult = "Error Encountered: " + e.Error.Message; }
else
{
BGW.ThisWorkersResult = "Task Completed Successfully";
//BGW.WorkersReturnObject = e.Result;
//background worker can't touch UI components
ListView_LocalComputers.ItemsSource = e.Result as List<DiscoveredComputer>;
}
}
I suggest these changes, or something similar, because the background worker can't modify/access UI components (like your ListView), so it has to pass back the value to use for the ListView view its Result property. I also included a simple way of detecting cancellation; I'll leave progress reporting up to you to implement.

How to receive property changed notifications in a Form when a property changes in a class?

I have a class with INotifyPropertyChanged interface. There is a property with the name Total Progress.
I have a Form with Progress Bar on it. I want to send the TotalProgress property changed notifications to this Progress Bar and set it's value.
Do I need to catch the PropertyChangedEvent in the Form also?
Edit: WPF Form Code
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
using System;
using System.Windows.Threading;
namespace SUpdater
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
BackgroundWorker bw = new BackgroundWorker();
DownloadFile FileDownloadClass = new DownloadFile();
public MainWindow()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
progressBar1.SetBinding(System.Windows.Controls.ProgressBar.ValueProperty, new Binding("TotalPercentCompleted"));
progressBar1.DataContext = FileDownloadClass;
FileDownloadClass.PropertyChanged +=new PropertyChangedEventHandler(FileDownloadClass_PropertyChanged);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
FileDownloadClass.DownloadFiles();
if ((bw.CancellationPending == true))
e.Cancel = true;
else
{
bw.ReportProgress(FileDownloadClass.TotalPercentCompleted);
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.lblConnectionStatus.Content = " Download Canceled!";
}
else if (!(e.Error == null))
{
this.lblConnectionStatus.Content = ("Error: " + e.Error.Message);
}
else
{
this.lblConnectionStatus.Content = "Done!";
}
}
private void FileDownloadClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lblKbCompleted.Content = e.ProgressPercentage.ToString();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
bw.RunWorkerAsync();
}
}
}
Edit: DownloadFile Class Code
sealed class DownloadFile:INotifyPropertyChanged
{
#region Private Fields
// These fields hold the values for the public properties.
private int progressBarValue = 0;
private int totalKbCompleted = 0;
private int totalBytesReceived = 0;
private int remoteFileSize = 0;
private string fileName = String.Empty;
private string statusMessage = String.Empty;
#endregion
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#region Public Properties
public int TotalKbCompleted
{
get { return this.totalKbCompleted; }
set
{
if (value != this.totalKbCompleted)
{
this.totalKbCompleted = value/1024;
NotifyPropertyChanged("TotalKbCompleted");
}
}
}
public int TotalBytesReceived
{
get { return this.totalBytesReceived; }
set
{
if (value != this.totalBytesReceived)
{
this.totalBytesReceived = value;
NotifyPropertyChanged("TotalBytesReceived");
}
}
}
public int RemoteFileSize
{
get { return this.remoteFileSize; }
set
{
if (value != this.remoteFileSize)
{
this.remoteFileSize = value;
NotifyPropertyChanged("RemoteFileSize");
}
}
}
public string CurrentFileName
{
get { return this.fileName; }
set
{
if (value != this.fileName)
{
this.fileName = value;
NotifyPropertyChanged("CurrentFileName");
}
}
}
public string StatusMessage
{
get { return this.statusMessage; }
set
{
if (value != this.statusMessage)
{
this.statusMessage = value;
NotifyPropertyChanged("StatusMessage");
}
}
}
#endregion
public Int16 DownloadFiles()
{
try
{
statusMessage = "Attempting Connection with Server";
DoEvents();
// create a new ftpclient object with the host and port number to use
FtpClient ftp = new FtpClient("mySite", 21);
// registered an event hook for the transfer complete event so we get an update when the transfer is over
//ftp.TransferComplete += new EventHandler<TransferCompleteEventArgs>(ftp_TransferComplete);
// open a connection to the ftp server with a username and password
statusMessage = "Connected. Authenticating ....";
ftp.Open("User Name", "Password");
// Determine File Size of the compressed file to download
statusMessage = "Getting File Details";
RemoteFileSize = Convert.ToInt32(ftp.GetFileSize("myFile.exe"));
ftp.TransferProgress += new EventHandler<TransferProgressEventArgs>(ftp_TransferProgress);
statusMessage = "Download from Server";
ftp.GetFile("myFile.exe", "E:\\Test\\myFile.exe", FileAction.Create);
// close the ftp connection
ftp.Close();
statusMessage = "Download Complete";
return 1;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
return 0;
}
}
private void ftp_TransferProgress(object sender, TransferProgressEventArgs e)
{
totalBytesReceived = Convert.ToInt32(e.BytesTransferred.ToString());
totalKbCompleted = Convert.ToInt32(totalKbCompleted + Convert.ToInt32(totalBytesReceived));
progressBarValue = totalKbCompleted;
}
}
You can use control binding:
Windows Forms:
progressBar1.DataBindings.Add("Value", dataSource, dataMember, true,
DataSourceUpdateMode.OnPropertyChanged);
where the dataSource is your class. and the dataMember is the property name in that class "TotalProgress".
Edit: For WPF
progressBar1.SetBinding(ProgressBar.ValueProperty, new Binding("ProgressTotal"));
progressBar1.DataContext = the instance of the class you want to bind to its property;
For more information about wpf data binding check this and this.
Edit2: Here is an full example:
Foo _foo = new Foo();
DispatcherTimer _dispatcherTimer = new DispatcherTimer();
public MainWindow()
{
InitializeComponent();
_dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
_dispatcherTimer.Tick += _dispatcherTimer_Tick;
_dispatcherTimer.Start();
progressBar1.SetBinding(ProgressBar.ValueProperty, new Binding("ProgressTotal"));
progressBar1.DataContext = _foo;
}
private void _dispatcherTimer_Tick(object sender, EventArgs e)
{
_foo.ProgressTotal = (_foo.ProgressTotal + 10) % progressBar1.Maximum;
}
public class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _progressTotal = 0;
public double ProgressTotal
{
get { return _progressTotal; }
set
{
if (value != _progressTotal)
{
_progressTotal = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ProgressTotal"));
}
}
}
}
}
Edit: Add a timer to preview the effect.
Edit: After you uploading your code, the problem appears in two positions:
The name of the variable is TotalKbCompleted no TotalPercentCompleted. so change the binding line to:
progressBar1.SetBinding(System.Windows.Controls.ProgressBar.ValueProperty, new Binding("TotalKbCompleted"));
You are updating the totalKbCompleted instead of TotalKbCompleted so the property changed will not trigger.

.NET Propertygrid refresh trouble

Property grid do not show new value of selected object.
For example:
o.val = "1";
pg.SelectedObject = o;
o.val = "2";
pg.Refresh();
The property in property grid is still "1";
It is changing only if you click on this property.
Or like that:
o.val = "1";
pg.SelectedObject = o;
o.val = "2";
pg.SelectedObject = o;
but in this case focus will be changed to PropertyGrid.
As I told you in my comment, your code is not enough to understand your issue. Presented like this it should work. Here is mine that works well:
public class Target
{
private int _myInt = 1;
public int MyInt { set; get; }
public static Target target = new Target();
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Button button = new Button()
{
Text = "Click me",
Dock = DockStyle.Bottom
};
Form form = new Form
{
Controls = {
new PropertyGrid {
SelectedObject = Target.target,
Dock = DockStyle.Fill,
},
button
}
};
button.Click += new EventHandler(button_Click);
Application.Run(form);
}
static void button_Click(object sender, EventArgs e)
{
Target.target.MyInt = 2;
Form form = Form.ActiveForm;
(form.Controls[0] as PropertyGrid).Refresh();
}
}
The call to Refresh() actually rebuilds the grid. Remove it and you will see the change only when you click the property.
Cause you just not gave some code, here is a working example.
Just put a Button and a PropertyGrid onto a form.
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class FormMain : Form
{
Random rand;
MyObject obj;
public FormMain()
{
InitializeComponent();
rand = new Random();
obj = new MyObject();
propertyGrid1.SelectedObject = obj;
}
private void button1_Click(object sender, EventArgs e)
{
obj.MyValue = rand.Next();
obj.IsEnabled = !obj.IsEnabled;
obj.MyText = DateTime.Now.ToString();
propertyGrid1.Refresh();
}
}
public class MyObject : INotifyPropertyChanged
{
private int _MyValue;
public int MyValue
{
get
{
return _MyValue;
}
set
{
_MyValue = value;
NotifyPropertyChanged("MyValue");
}
}
private string _MyText;
public string MyText
{
get
{
return _MyText;
}
set
{
_MyText = value;
NotifyPropertyChanged("MyText");
}
}
private bool _IsEnabled;
public bool IsEnabled
{
get
{
return _IsEnabled;
}
set
{
_IsEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}

Categories

Resources