I'm writing a desktop application in Visual Studio 2008 / C#
I finished (mostly) writing an engine which generates a working schedule for the week for a small company; a form of a course-scheduling problem
Right now I'm designing a form in which the user can determine the initial parameters, or criteria, for the engine to adhere to (as some settings are optional)
I've got a class named EngineParameters, which holds all of those settings.
For the purpose of databinding, I created a bndEngineParameters class, which encapsulates all the relevant fields with getters and setters
public class bndEngineParameters
{
private engineParameters _parameters;
public bndEngineParameters(engineParameters ep)
{
this._parameters = ep;
}
public bool avoidGrouping
{
get { return _parameters.avoidGrouping; }
set { _parameters.avoidGrouping = value; }
}
public bool avoidWeekends
{
get { return _parameters.avoidWeekends; }
set { _parameters.avoidWeekends = value; }
}
public bool keyFlow
{
get { return _parameters.keyFlow; }
set { _parameters.keyFlow = value; }
}
public bool keyFlowAssistants
{
get { return _parameters.keyFlowAssistants; }
set { _parameters.keyFlowAssistants = value; }
}
}
It's not complete - there will be int values (maximum number of hours one can work etc.); I want those bool values to be bound to checkboxes on my form
And it's at that trivial task where I surprisingly ran into problems
Using "Add New DataSource" wizard, I created a binding source
private System.Windows.Forms.BindingSource bndEngineParametersBindingSource;
I then bound the Checked property of my Checkbox to the respective property of my binding source:
I implemented a local variable boundParameters so that I get an access to the parameters set by the user
public partial class formGenerateRota : Form
{
public bndEngineParameters boundParameters;
// (...)
public formGenerateRota()
{
InitializeComponent();
}
private void formGenerateRota_Load(object sender, EventArgs e)
{
boundParameters = new bndEngineParameters(new engineParameters());
bndEngineParametersBindingSource.Add(boundParameters);
}
// (...)
}
And what? Nothing happens. There is an bndEngineParameters object under bndEngineParametersBindingSource.Current (in run-time of course), but the avoidWeekends value never changes (when I check the checkbox on and off), and the bndEngineParametersBindingSource_CurrentItemChanged event is never fired
What's wrong?
SORRY! it does change, but only after the checkbox loses focus (after validation).
I'm stupid sometimes
If I'm doing something wrong anyway (I'm not any good with data binding), I'd very much appreciate if you point it out of course!
Two common issues:
set the DataSourceUpdateMode to OnPropertyChanged
(optional) to receive changes from the object, implement the {name}Changed event pattern or INotifyPropertyChanged
To be honest though, I'm sure most of that isn't necessary; you should just be able to say:
myCheckbox.Bindings.Add("Checked", myEngineParameters, "avoidWeekends",
false, DataSourceUpdateMode.OnPropertyChanged);
Full example:
using System;
using System.Diagnostics;
using System.Windows.Forms;
class EngineParameters {
private bool avoidWeekends;
public bool AvoidWeekends {
get { return avoidWeekends; }
set {
avoidWeekends = value;
Debug.WriteLine("AvoidWeekends => " + value);
}
}
}
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
using(Form form = new Form())
using (CheckBox myCheckbox = new CheckBox()) {
EngineParameters myEngineParameters = new EngineParameters();
myEngineParameters.AvoidWeekends = true;
form.Controls.Add(myCheckbox);
myCheckbox.DataBindings.Add("Checked", myEngineParameters, "AvoidWeekends",
false, DataSourceUpdateMode.OnPropertyChanged);
Application.Run(form);
}
}
}
Instead of this:
bndEngineParametersBindingSource.Add(boundParameters);
do this:
bndEngineParametersBindingSource.DataSource = boundParameters;
Related
I have 2 forms: Form A and Form B. I also have a property field class.
Form A contains the label I want changed when a property is changed. Form B contains code that will change the property field.
Property Class Code:
public class Controller
{
private static string _customerID;
public static string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
FormA.ChangeMe();
}
}
}
}
Form B Code:
private void something_Click(object sender, SomethingEventArgs e) {
Controller.customerID = "Cool";
}
Form A Code:
public static void ChangeMe()
{
var frmA = new FormA();
MessageBox.Show("Test: " + Controller.customerID); //This works! Shows Cool
frmA.lb2Change.Text = Controller.customerID; //This kind of works..
MessageBox.Show("Test2: " + frmA.lb2Change.Text); //This shows the correct value. Shows Cool
}
The property field value is passed (which I know from the MessageBox) however it does not update the value on the form label itself. Why is this? What am I doing wrong? I also believe there is a better alternative for achieving what ChangeMe() method is intended to achieve -- if so are there any suggestions?
You can do the following
To define a delegate
To Implement Property Change Notification
Delegate
public delegate void OnCustomerIDChanging(object sender,CancelEventArgs e);
public delegate void OnCustomerIDChanged(object sender,object value);
public class Controller
{
private static string _customerID;
public event OnCustomerIDChanging CustoerIDChanging;
public event OnCustomerIDChanged CustoerIDChanged;
public static string customerID
{
get { return _customerID; }
set
{
// make sure that the value has a `value` and different from `_customerID`
if(!string.IsNullOrEmpty(value) && _customerID!=value)
{
if(CustomerIDChanging!=null)
{
var state = new CancelEventArgs();
// raise the event before changing and your code might reject the changes maybe due to violation of validation rule or something else
CustomerIDChanging(this,state);
// check if the code was not cancelled by the event from the from A
if(!state.Cancel)
{
// change the value and raise the event Changed
_customerID = value;
if(CustomerIDChanged!=null)
CustomerIDChanged(this,value);
}
}
}
}
}
}
in your Form and when you are initiating the Controller Object
var controller = new Controller();
controller.CustomerIDChanging +=(sd,args) =>{
// here you can test if you want really to change the value or not
// in case you want to reject the changes you can apply
args.Cancel = true;
};
controller.CustomerIDChanged +=(sd,args) =>{
// here you implement the code **Changed already**
}
The above code will give you a great control over your code, also will make your controller code reusable and clean. Same
result you can get by implementing INotifyPropertyChanged interface
INotifyPropertyChanged
you might have a look on this article to get more information
In your static method ChangeMe you are creating a new Form every time, you want to Change the value. Instead of that you want to change the value of an existing form. Therefor your Controller needs an instance of this FormA. Try it like this:
public class Controller
{
//You can pass the form throught the constructor,
//create it in constructor, ...
private FormA frmA;
private string _customerID;
public string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
frmA.ChangeMe();
}
}
}
}
Now you donĀ“t need to be static in your FormA:
public void ChangeMe()
{
MessageBox.Show("Test: " + Controller.customerID);
this.lb2Change.Text = Controller.customerID;
}
I find myself quite often in the following situation:
I have a user control which is bound to some data. Whenever the control is updated, the underlying data is updated. Whenever the underlying data is updated, the control is updated. So it's quite easy to get stuck in a never ending loop of updates (control updates data, data updates control, control updates data, etc.).
Usually I get around this by having a bool (e.g. updatedByUser) so I know whether a control has been updated programmatically or by the user, then I can decide whether or not to fire off the event to update the underlying data. This doesn't seem very neat.
Are there some best practices for dealing with such scenarios?
EDIT: I've added the following code example, but I think I have answered my own question...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
One option would be to stop the update in case the data didn't change since the last time. For example if the data were in form of a class, you could check if the data is the same instance as the last time the event was triggered and if that is the case, stop the propagation.
This is what many MVVM frameworks do to prevent raising PropertyChanged event in case the property didn't actually change:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
You can implement this concept similarly for Windows Forms.
What you're looking for is called Data Binding. It allows you to connect two or more properties, so that when one property changes others will be updated auto-magically.
In WinForms it's a little bit ugly, but works like a charm in cases such as yours. First you need a class which represents your data and implements INotifyPropertyChanged to notify the controls when data changes.
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Than in your Form/Control you bind the value of ViewModel.TextFieldValue to textBox.Text. This means whenever value of TextFieldValue changes the Text property will be updated and whenever Text property changes TextFieldValue will be updated. In other words the values of those two properties will be the same. That solves the circular loops issue you're encountering.
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
If you need to modify the values from outside of the Form/Control, simply set values of the ViewModel
form.ViewModel.TextFieldValue = "new value";
The control will be updated automatically.
You should look into MVP - it is the preferred design pattern for Winforms UI.
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
using that design pattern gives you a more readable code in addition to allowing you to avoid circular events.
in order to actually avoid circular events, your view should only export a property which once it is set it would make sure the txtChanged_Event would not be called.
something like this:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
or you can use a MZetko's answer with a private property
I have these objects in my project:
SchedulerList
SchedulerListItem
SchedulerListItemDetails
each one is a win forms control, which are used in forms of my application. The SchedulerList holds SchedulerListItems and each item can have SchedulerListItemDetails.
my code goes as follows:
//creating my initial list form
FrmListTesting f = new FrmListTesting();
f.Show();
The form has only one button that has a hard-coded parameter for testing purposes, as well as a SchedulerList control taht will hold the list items.
When the button is clicked the form does the following:
private void button1_Click(object sender, EventArgs e)
{
var control = this.Controls[1] as SchedulerList;
var path = #"D:\Share\Countries.txt";
var sli = new SchedulerListItem(path);
control.AddItem(sli);
}
my SchedulerListItem constuctor goes as follows:
public SchedulerListItem(string path)
{
InitializeComponent();
this.Name = Path.GetFileNameWithoutExtension(path);
this.SourcePath = path;
this.DestinationPath = GetDestinationPath(path);
}
And the AddItem method is defined as:
public void AddItem(SchedulerListItem item)
{
this.flPanel.Controls.Add(item);
}
The add item method works as intended, displays all the data that was required and displays it in the UI. The list item has a button that brings up the details form as such:
//the form constructor
public FrmSchedulerItemDetails(SchedulerListItem item)
{
InitializeComponent();
this.detailsControl = new SchedulerListItemDetails(item, this);
}
//control constructor
public SchedulerListItemDetails(SchedulerListItem item, Form owner)
{
InitializeComponent();
this.SourcePath = item.SourcePath;
this.DestinationPath = item.DestinationPath;
this.OldFormat = item.OldFormat;
this.ExportToExcel = item.ExportToExcel;
this.owner = owner;
this.underlyingItem = item;
}
And now the problem. After the SchedulerListItemDetails constructor is called and the data "gets initialized", when i look at the data inside the object its set to default values. it seams that everything that I set after InitializeComponent(); gets ignored.
things that i have tried:
hard-coding the values to see if primitives get passed correctly
settings breakpoints on every InitializeComponent() method to see the stack trace associated with setting to default values
none of the methods show any results... I know that if i use a form directly instead of using a control within a from i can set the values the way i want to, but I'm very confused as to why this other method with controls doesn't work.
EDIT 1:
the code for SchedulerListItemDetails:
public partial class SchedulerListItemDetails : UserControl
{
public SchedulerListItemDetails(SchedulerListItem item, Form owner)
{
InitializeComponent();
this.SourcePath = item.SourcePath;
this.DestinationPath = item.DestinationPath;
this.OldFormat = item.OldFormat;
this.ExportToExcel = item.ExportToExcel;
this.owner = owner;
this.underlyingItem = item;
}
public SchedulerListItemDetails()
{
InitializeComponent();
}
private Form owner = null;
private SchedulerListItem underlyingItem;
public Boolean ExportToExcel
{
get
{
return this.cbxExcel.Checked;
}
set
{
this.cbxExcel.Checked = value;
}
}
public Boolean OldFormat
{
get
{
return this.cbxOldFormat.Checked;
}
set
{
this.cbxOldFormat.Checked = value;
}
}
public String DestinationPath
{
get
{
return this.tbxDestinationPath.Text;
}
set
{
this.tbxDestinationPath.Text = value;
}
}
public String SourcePath
{
get
{
return this.tbxSourcePath.Text;
}
set
{
this.tbxSourcePath.Text = value;
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.owner.Close();
}
private void btnSave_Click(object sender, EventArgs e)
{
underlyingItem.SourcePath = this.SourcePath;
underlyingItem.DestinationPath = this.DestinationPath;
underlyingItem.OldFormat = this.OldFormat;
underlyingItem.ExportToExcel = this.ExportToExcel;
btnCancel_Click(sender, e);
}
}
I'll make an answer, because it should help you to solve your problem.
You have default (parameterless) constructor, which may be called and if it is called, then your constructor with parameters is not called.
Proper design would be something like
public partial class SchedulerListItemDetails : UserControl
{
public SchedulerListItemDetails()
{
InitializeComponent();
}
public SchedulerListItemDetails(SchedulerListItem item, Form owner): this()
{
this.SourcePath = item.SourcePath;
...
}
}
Notice this(), this ensure what parameterless constructor is called before (and InitializeComponent() as well, no need to duplicate it in another constructor).
Back to your problem. In your case it's like this
public partial class SchedulerListItemDetails : UserControl
{
public SchedulerListItemDetails()
{
InitializeComponent();
}
public SchedulerListItemDetails(SchedulerListItem item, Form owner)
{
InitializeComponent();
this.SourcePath = item.SourcePath;
...
}
}
Only one constructor can be called. So if you put breakpoint in parameterless one and it's triggered, then you have problems. Because you create somewhere SchedulerListItemDetails without setting it's properties (they stay default).
More likely problem is that you create new instance of that object (either before or after constructing proper, if your code ever construct such object) and that instance is what you inspect later.
So after i got a quick course of how win forms work i figured out what the problem was.
my code that i thought was enough is:
public FrmSchedulerItemDetails(SchedulerListItem item)
{
InitializeComponent();
this.DetailsControl = new SchedulerListItemDetails(item, this);
}
public SchedulerListItemDetails DetailsControl
{
get
{
return this.detailsControl;
}
set
{
this.detailsControl = value;
}
}
the this.detailsControl is the control im trying to setup, but as i have learned the correct way of replacing a component for a new one is:
public FrmSchedulerItemDetails(SchedulerListItem item)
{
InitializeComponent();
this.DetailsControl = new SchedulerListItemDetails(item, this);
}
public SchedulerListItemDetails DetailsControl
{
get
{
return this.detailsControl;
}
set
{
this.Controls.Remove(this.detailsControl);
this.detailsControl = value;
this.Controls.Add(this.detailsControl);
}
}
Feel kinda silly now :).
I am running into an issue that I have found on some similar post, however, they are not quite the same and I am not quite sure how to apply it to my scenario. They may or may not be the same as my case. So, I am posting my own question here hopefully, I will get an answer to my specific scenario.
Basically, I have a window form with a bunch of controls. I would like to have the ability to bind their Enabled property to a Boolean variable that I set so that they can be enable or disable to my discretion.
public partial class MyUI : Form
{
private int _myID;
public int myID
{
get
{
return _myID;;
}
set
{
if (value!=null)
{
_bEnable = true;
}
}
}
private bool _bEnable = false;
public bool isEnabled
{
get { return _bEnable; }
set { _bEnable = value; }
}
public myUI()
{
InitializeComponent();
}
public void EnableControls()
{
if (_bEnable)
{
ctl1.Enabled = true;
ctl2.Enabled = true;
......
ctl5.Enabled = true;
}
else
{
ctl1.Enabled = false;
ctl2.Enabled = false;
......
ctl5.Enabled = false;
}
}
}
}
The method EnableControls above would do what I need but it may not be the best approach. I prefer to have ctrl1..5 be bound to my variable _bEnable. The variable will change depending on one field users enter, if the value in the field exists in the database, then other controls will be enabled for user to update otherwise they will be disabled.
I have found a very similar question here
but the data is bound to the text field. How do I get rid of the EnableControls method and bind the value of _bEnabled to the "Enabled" property in each control?
Go look into the MVVM (Model - View - ViewModel) pattern, specifically its implementation within Windows Forms. Its much easier to apply it to a WPF/Silverlight application, but you can still use it with Windows Forms without too much trouble.
To solve your problem directly, you will need to do 2 things:
Create some class that will hold your internal state (i.e. whether or not the buttons are enabled). This class must implement INotifyPropertyChanged. This will be your View Model in the MVVM pattern.
Bind an instance of the class from 1.) above to your Form. Your form is the View in the MVVM pattern.
After you have done 1 and 2 above, you can then change the state of your class (i.e. change a property representing whether a button is enabled from true to false) and the Form will be updated automatically to show this change.
The code below should be enough to get the concept working. You will need to extend it obviously, but it should be enough to get you started.
View Model
public class ViewModel : INotifyPropertyChanged
{
private bool _isDoStuffButtonEnabled;
public bool IsDoStuffButtonEnabled
{
get
{
return _isDoStuffButtonEnabled;
}
set
{
if (_isDoStuffButtonEnabled == value) return;
_isDoStuffButtonEnabled = value;
RaisePropertyChanged("IsDoStuffButtonEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View
public class View : Form
{
public Button DoStuffButton { get; set; }
public void Bind(ViewModel vm)
{
DoStuffButton.DataBindings.Add("Enabled", vm, "IsDoStuffButtonEnabled");
}
}
Usage
public class Startup
{
public ViewModel ViewModel { get; set; }
public View View { get; set; }
public void Startup()
{
ViewModel = new ViewModel();
View = new View();
View.Bind(ViewModel);
View.Show();
ViewModel.IsDoStuffButtonEnabled = true;
// Button becomes enabled on form.
// ... other stuff here.
}
}
Maybe you can try this approach: in your isEnabled property's setter method, add an if statement:
if(_bEnable) EnableControls();
else DisableControls();
And if your control names are ctl1,ctl2... etc. you can try this:
EnableControls()
{
for(int i=1; i<6;i++)
{
string controlName = "ctl" + i;
this.Controls[controlName].Enabled = true;
}
}
And apply the same logic in DisableControls
If you have more controls in future this could be more elegant.
I'm writing an android app that uses the MediaPlayer. I've created a custom IAudioPlayer as a wrapper for the MediaPlayer so that I can eventually extend it to iOS.
public interface IAudioPlayer
{
bool IsPlaying { get; }
void Play(string fileName, int startingPoint);
void Play(string fileName);
void Pause();
void Stop();
int CurrentPosition();
bool HasFile();
void SkipForward(int seconds);
void SkipBackward(int seconds);
void SeekTo(int seconds);
}
Our app is structured using the Mvvm pattern and is using MvvmCross.
On our FragmentViewModel we've got commands such as IPlayCommand, IStopCommand, etc.
public class HomeFragmentViewModel : ViewModelBase
{
public HomeFragmentViewModel(IPlayCommand playCommand,
IStopCommand stopCommand,
ISkipForwardCommand skipForwardCommand,
ISkipBackwardCommand skipBackwardCommand)
{
_playCommand = playCommand;
_stopCommand = stopCommand;
_skipForwardCommand = skipForwardCommand;
_skipBackwardCommand = skipBackwardCommand;
}
private string _playPauseIcon = FontAwesome.icon_play;
public string PlayPauseIcon
{
get { return _playPauseIcon; }
set { _playPauseIcon = value; RaisePropertyChanged(() => PlayPauseIcon); }
}
private IPlayCommand _playCommand;
public IPlayCommand PlayCommand
{
get { return _playCommand; }
set { _playCommand = value; RaisePropertyChanged(() => PlayCommand); }
}
private IStopCommand _stopCommand;
public IStopCommand StopCommand
{
get { return _stopCommand; }
set { _stopCommand = value; RaisePropertyChanged(() => StopCommand); }
}
private ISkipForwardCommand _skipForwardCommand;
public ISkipForwardCommand SkipForwardCommand
{
get { return _skipForwardCommand; }
set { _skipForwardCommand = value; RaisePropertyChanged(() => SkipForwardCommand); }
}
private ISkipBackwardCommand _skipBackwardCommand;
public ISkipBackwardCommand SkipBackwardCommand
{
get { return _skipBackwardCommand; }
set { _skipBackwardCommand = value; RaisePropertyChanged(() => SkipBackwardCommand); }
}
}
On our View we've got buttons that bind to those commands, and all is working as expected.
However
Our view also has a SeekBar that we're going to use for quick scrubbing by the user. The seekbar needs to do two things...
allow the user quickly navigate to the spot that's required (this one "should" be easy
automatically update based on the track's current progress (this one I'm stuck on)
How can I write a notifier that triggers every second, and automatically update the seekbar binding? This notifier would need to live in the PCL with the Command Objects so that it can work cross platform. I'm struggling with getting started on this... I'm not sure where to create it or how to wire it up.
automatically update based on the track's current progress (this one I'm stuck on)
You should be able to do this using a binding to a View property. Here's some pseudo code:
In the ViewModel, add the SeekPosition property:
public double SeekPosition { /* normal INPC get/set */ }
In the View:
add a property and event pair like:
public event EventHandler CurrentPositionChanged;
public double CurrentPosition
{
get { return _mediaPlayer.CurrentPosition; }
set
{
_mediaPlayer.SeekTo(value);
}
}
add a timer to fire the CurrentPositionChanged event on the UI thread. For Android, create this timer in OnResume and destroy it in OnPause.
add a binding in OnCreate:
public override void OnCreate(args)
{
// normal base call and inflate
// ...
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(this).For(v => v.CurrentPosition).To(vm => vm.SeekPosition);
set.Apply();
}
Note that this approach doesn't use a timer in the PCL. This is because other platforms like iOS and Windows shouldn't need the timer - as they should be able to use progress callbacks/events from the media players on those platforms instead.