|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis solution demonstrates a simple model-view pattern for a multithreaded Windows Forms UI in .NET 2.0. I prefer to design the "model" classes for my programs first, and then design the forms around the model. This example shows a couple of types of data binding techniques using worker threads and timers. Don't underestimate the simplicity of these examples, you can build incredibly complex user interfaces using a model based approach. Perhaps, this article will help you solve similar problems faster; I couldn't find anything on CodeProject covering this topic. Databinding with models, threads, and timersNote: The model class uses the For simplicity, a static instance of the model class is created in the Example 1. You can bind a control to a property of an arbitrary class. The first example shows three controls bound to the model's To set up data binding in the form: textBoxScrollValue.DataBindings.Add("Text",
Program.Model, "ScrollValue", false,
DataSourceUpdateMode.OnPropertyChanged);
trackBar1.DataBindings.Add("Value", Program.Model,
"ScrollValue", false,
DataSourceUpdateMode.OnPropertyChanged);
numericUpDown1.DataBindings.Add("Value", Program.Model,
"ScrollValue", false,
DataSourceUpdateMode.OnPropertyChanged);
int scrollValue = 0;
public int ScrollValue
{
get { return scrollValue; }
set
{
scrollValue = value;
// Not being set from a worker thread, so
// UpdateObservers not required, let the
// default binding occur.
}
}
Example 2. In some cases, you will need a background timer to perform periodic tasks: read hardware settings, read databases, examine system performance counters, etc. As a matter of preference, I like to abstract these operations into a model class. When the timer fires, it updates the model's data, and the model updates all of its observers. All cross-threading issues with the timer and UI threads are automatically handled in double power = 0;
public double Power
{
get { return power; }
set
{
power = value;
UpdateObservers("Power", power);
}
}
Note: In the first version of the project, I ran into deadlock problems when trying to lock on the observer collection in Lock on the observer collection array's private void UpdateObservers(string propertyName, object value)
{
Array copy;
lock (observers.SyncRoot)
{
copy = observers.ToArray();
}
for (int n = 0; n < copy.Length; n++)
{
Control control = (Control)copy.GetValue(n);
// Handle must exist.
if (!control.IsHandleCreated)
continue;
if (control.IsDisposed)
continue;
switch (propertyName)
{
case "Power":
control.Invoke(((MainForm)control).PowerDelegate,
new object[] { (double)value });
break;
case "StateFlag":
control.Invoke(((MainForm)control).PowerButtonDelegate,
new object[] { (bool)value });
break;
}
}
} // UpdateObservers
Example 3. Shows the use of a background thread that performs some sort of time consuming or repetitive operation -- in this case, it updates a random number in the model every 100 mSec. Cross-threading issues don't arise since all UI updates are handled by invoking a delegate in I've added an example of In the model class: public ComboBindingList<PreampEnum> preampList =
new ComboBindingList<PreampEnum>();
private PreampEnum preampSetting = PreampEnum.OFF;
public PreampEnum PreampSetting {
get { return preampSetting; }
set { preampSetting = value; } }
...
preampList.Add(new ComboHelper<PreampEnum>("Off",
PreampEnum.OFF));
preampList.Add(new ComboHelper<PreampEnum>("Low",
PreampEnum.LOW));
preampList.Add(new ComboHelper<PreampEnum>("Medium",
PreampEnum.MEDIUM));
preampList.Add(new ComboHelper<PreampEnum>("High",
PreampEnum.HIGH));
Then set the binding in the form class: comboBox1.DataSource = Program.Model.preampList;
comboBox1.DisplayMember = "DisplayName";
comboBox1.ValueMember = "Value";
comboBox1.DataBindings.Add("SelectedValue", Program.Model,
"PreampSetting", false,
DataSourceUpdateMode.OnPropertyChanged);
For reference, if you don't want to download the source, here are the two support classes: public class ComboHelper<T>
{
protected string displayName;
protected T settingValue;
public ComboHelper(string paramName, T paramValue)
{
displayName = paramName;
settingValue = paramValue;
}
public override string ToString()
{
return displayName;
}
public string DisplayName { get { return displayName; }
set { displayName = value; } }
public T Value { get { return settingValue; }
set { settingValue = value; } }
}
public class ComboBindingList<T> :
CollectionBase, IBindingList
{
private ListChangedEventArgs resetEvent = new
ListChangedEventArgs(ListChangedType.Reset, -1);
private ListChangedEventHandler onListChanged;
public ComboHelper<T> this[int index]
{
get
{
return (ComboHelper<T>)(List[index]);
}
set
{
List[index] = value;
}
}
public int Add (ComboHelper<T> value)
{
return List.Add(value);
}
public ComboHelper<T> AddNew()
{
return (ComboHelper<T>)
((IBindingList)this).AddNew();
}
public void Remove (ComboHelper<T> value)
{
List.Remove(value);
}
protected virtual void OnListChanged(ListChangedEventArgs ev)
{
if (onListChanged != null)
{
onListChanged(this, ev);
}
}
protected override void OnClear()
{
}
protected override void OnClearComplete()
{
OnListChanged(resetEvent);
}
protected override void OnInsertComplete(int index, object value)
{
ComboHelper<T> c = (ComboHelper<T>)value;
OnListChanged(new ListChangedEventArgs(
ListChangedType.ItemAdded, index));
}
protected override void OnRemoveComplete(int index, object value)
{
ComboHelper<T> c = (ComboHelper<T>)value;
OnListChanged(new ListChangedEventArgs(
ListChangedType.ItemDeleted, index));
}
protected override void OnSetComplete(int index,
object oldValue, object newValue)
{
if (oldValue != newValue)
{
OnListChanged(new ListChangedEventArgs(
ListChangedType.ItemAdded, index));
}
}
// Called by ComboHelper<T> when it changes.
internal void ComboHelper_Changed(ComboHelper<T> cust)
{
int index = List.IndexOf(cust);
OnListChanged(new ListChangedEventArgs(
ListChangedType.ItemChanged, index));
}
// Implements IBindingList.
bool IBindingList.AllowEdit
{
get { return true ; }
}
bool IBindingList.AllowNew
{
get { return true ; }
}
bool IBindingList.AllowRemove
{
get { return true ; }
}
bool IBindingList.SupportsChangeNotification
{
get { return true ; }
}
bool IBindingList.SupportsSearching
{
get { return false ; }
}
bool IBindingList.SupportsSorting
{
get { return false ; }
}
// Events.
public event ListChangedEventHandler ListChanged
{
add
{
onListChanged += value;
}
remove
{
onListChanged -= value;
}
}
// Methods.
object IBindingList.AddNew()
{
ComboHelper<T> c = new ComboHelper<T>("",
(T)new object());
List.Add(c);
return c;
}
// Unsupported properties.
bool IBindingList.IsSorted
{
get { throw new NotSupportedException(); }
}
ListSortDirection IBindingList.SortDirection
{
get { throw new NotSupportedException(); }
}
PropertyDescriptor IBindingList.SortProperty
{
get { throw new NotSupportedException(); }
}
// Unsupported Methods.
void IBindingList.AddIndex(PropertyDescriptor property)
{
throw new NotSupportedException();
}
void IBindingList.ApplySort(PropertyDescriptor property,
ListSortDirection direction)
{
throw new NotSupportedException();
}
int IBindingList.Find(PropertyDescriptor property, object key)
{
throw new NotSupportedException();
}
void IBindingList.RemoveIndex(PropertyDescriptor property)
{
throw new NotSupportedException();
}
void IBindingList.RemoveSort()
{
throw new NotSupportedException();
}
}
Using the codeBuild it and run it. Move the Points of InterestVisual Studio 2005 helps you eliminate "cross-threading" errors in your code. In debug builds, you'll get cross-threading exceptions whenever it detects a threading error. In release builds, this exception is disabled. History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||