I have a base class implementing INotifyPropertyChanged:
protected void OnNotifyChanged(string pName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
I have a derived class with a property Latitude like so:
private double latitude;
public double Latitude
{
get { return latitude; }
set { latitude = value; OnNotifyChanged("Latitude"); }
}
My derived class also has a method Fly that manipulates Latitude.
I also have a Form with a TextBox bound to Latitude of my derived class:
txtLat.DataBindings.Clear();
txtLat.DataBindings.Add("Text", bindSrc, "Latitude");
A thread is used to kick off Fly like so:
Thread tFly = new Thread(f.Fly);
tFly.IsBackground = true;
tFly.Start();
When Latitude changes, an exception is thrown:
DataBinding cannot find a row in the list that is suitable for all bindings.
This seems to be an odd issue with thread affinity. Ultimately, the code is trying to do the update from a non-UI thread - I'm unclear why it isn't just displaying the cross-thread exception, though - I wonder whether this is actually a catch-all exception handler. If I remove the BindingSource (and bind directly to the object, which is valid) you do get a cross-thread exception (which I expected).
Personally, I would be inclined to handle this manually, i.e. subscribe to the event with a method that does an Invoke to the UI thread and updates the Text manually. However, I'm just checking if some previous cross-threaded binding code might help...
Here's an example using Invoke:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
class FlightUav : INotifyPropertyChanged
{
protected void OnNotifyChanged(string pName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(pName));
}
public event PropertyChangedEventHandler PropertyChanged;
private double _latitude;
public double Latitude
{
get { return _latitude; }
set { _latitude = value; OnNotifyChanged("Latitude"); }
}
public void Fly()
{
for (int i = 0; i < 100; i++)
{
Latitude++;
Thread.Sleep(10);
}
}
[STAThread]
static void Main()
{
using (Form form = new Form())
{
FlightUav currentlyControlledFlightUav = new FlightUav();
currentlyControlledFlightUav.PropertyChanged += delegate
{ // this should be in a *regular* method so that you can -= it when changing bindings...
form.Invoke((MethodInvoker)delegate
{
form.Text = currentlyControlledFlightUav.Latitude.ToString();
});
};
using (Button btn = new Button())
{
btn.Text = "Fly";
btn.Click += delegate
{
Thread tFly = new Thread(currentlyControlledFlightUav.Fly);
tFly.IsBackground = true;
tFly.Start();
};
form.Controls.Add(btn);
Application.Run(form);
}
}
}
}
Here's an example using a (modified) version of some old threading code of mine:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
class FlightUav : INotifyPropertyChanged
{
protected void OnNotifyChanged(string pName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(pName));
}
public event PropertyChangedEventHandler PropertyChanged;
private double _latitude;
public double Latitude
{
get { return _latitude; }
set { _latitude = value; OnNotifyChanged("Latitude"); }
}
public void Fly()
{
for (int i = 0; i < 100; i++)
{
Latitude++;
Thread.Sleep(10);
}
}
[STAThread]
static void Main()
{
using (Form form = new Form())
{
FlightUav currentlyControlledFlightUav = new FlightUav();
BindingSource bindSrc = new BindingSource();
var list = new ThreadedBindingList<FlightUav>();
list.Add(currentlyControlledFlightUav);
bindSrc.DataSource = list;
form.DataBindings.Clear();
form.DataBindings.Add("Text", list, "Latitude");
using (Button btn = new Button())
{
btn.Text = "Fly";
btn.Click += delegate
{
Thread tFly = new Thread(currentlyControlledFlightUav.Fly);
tFly.IsBackground = true;
tFly.Start();
};
form.Controls.Add(btn);
Application.Run(form);
}
}
}
}
public class ThreadedBindingList<T> : BindingList<T>
{
private readonly SynchronizationContext ctx;
public ThreadedBindingList()
{
ctx = SynchronizationContext.Current;
}
protected override void OnAddingNew(AddingNewEventArgs e)
{
SynchronizationContext ctx = SynchronizationContext.Current;
if (ctx == null)
{
BaseAddingNew(e);
}
else
{
ctx.Send(delegate
{
BaseAddingNew(e);
}, null);
}
}
void BaseAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (ctx == null)
{
BaseListChanged(e);
}
else
{
ctx.Send(delegate
{
BaseListChanged(e);
}, null);
}
}
void BaseListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
}
}
Related
This works (the graph is properly loaded):
var oxyPlotView = new OxyPlotView{ Model = GetPlotModelSynch() };
This doesn't (the graph remains empty):
var oxyPlotView = new OxyPlotView();
// Here PlotModel will be loaded asynchronously from the BindingContext:
oxyPlotView.SetBinding(OxyPlotView.ModelProperty, new Binding(nameof(GraphViewModel.PlotModel)));
I have made proper isolated tests to ensure that INotifyPropertyChanged is working properly with my ViewModel. So the problem seems to be that OxyPlotView is built properly only if it has al the info from its inception (?). Is that even possible?
Here is the full ViewModel. INotifyPropertyChanged works because Title is behaving as intended (Title is binded to a Label in the same view).
class GraphViewModel : INotifyPropertyChanged
{
IGraphSeriesGroupRepository _graphSeriesGroupRepository;
private GraphSeriesGroup _graphSeriesGroup;
private ulong _sensorId;
public event PropertyChangedEventHandler PropertyChanged;
private PlotModel _plotModel;
public PlotModel PlotModel
{
get { return _plotModel; }
set
{
if (_plotModel != value)
{
_plotModel = value;
OnPropertyChanged(nameof(PlotModel));
}
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
OnPropertyChanged(nameof(Title));
}
}
}
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged(nameof(IsLoading));
}
}
public GraphViewModel(IGraphSeriesGroupRepository graphSeriesGroupRepository, ulong sensorId)
{
_graphSeriesGroupRepository = graphSeriesGroupRepository;
_sensorId = sensorId;
Load();
}
public PlotModel GetPlotModelSynch()
{
_graphSeriesGroup = _graphSeriesGroupRepository.GetGraphSeriesGroup(_sensorId);
return GetPlotModel(_graphSeriesGroup);
}
private async void Load()
{
IsLoading = true;
await Task.Delay(5000);
_graphSeriesGroup = await _graphSeriesGroupRepository.GetGraphSeriesGroupAsync(_sensorId);
ApplyChanges();
IsLoading = false;
}
private void ApplyChanges()
{
// ---
Title = _graphSeriesGroup.Title;
PlotModel = GetPlotModel(_graphSeriesGroup);
}
private PlotModel GetPlotModel(GraphSeriesGroup graphSeriesGroup)
{
...
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Update: The only way I've found to make it work is:
private void chatter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(GraphViewModel.PlotModel))
{
_oxyPlotView = new OxyPlotView
{
Model = _graphViewModel.PlotModel
};
_stackLayout.Children.Add(_oxyPlotView);
}
}
...even updating an _oxyPlotView (which was already added to the StackLayout) and calling _oxyPlotView.InvalidateDisplay() didn't work.
So I have this object:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Suppose I am changing this object on a thread that is NOT the GUI thread. How can I have this object raise the PropertyChanged event on the same thread as the GUI when I don't have a reference to any GUI component in this class?
Normally the event subscriber should be responsible for marshalling the calls to the UI thread if necessary.
But if the class in question is UI specific (a.k.a view model), as soon it is created on the UI thread, you can capture the SynchronizationContext and use it for raising the event like this:
public class SomeObject : INotifyPropertyChanged
{
private SynchronizationContext syncContext;
public SomeObject()
{
syncContext = SynchronizationContext.Current;
}
private decimal alertLevel;
public decimal AlertLevel
{
get { return alertLevel; }
set
{
if (alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
if (syncContext != null)
syncContext.Post(_ => handler(this, new PropertyChangedEventArgs(propertyName)), null);
else
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Alternatively you can pass SynchronizationContext via constructor.
Yet another way is to keep the object intact, but data bind to it via intermediate synchronized binding source as described here Update elements in BindingSource via separate task.
for WPF - Add the following references:
PresentationFramework.dll
WindowsBase.dll
In your background thread - wrap the code that needs access to UI into a dispatcher.Invoke()
using System.Windows;
using System.Windows.Threading;
...
//this is needed because Application.Current will be NULL for a WinForms application, since this is a WPF construct so you need this ugly hack
if (System.Windows.Application.Current == null)
new System.Windows.Application();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
//Do Your magic here
}), DispatcherPriority.Render);
for WinForms use
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(() => {
//Do Your magic here
}));
An even better idea, without using any WPF references:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private WeakReference itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init(Control ctrl) {
itsDispatcher = new WeakReference(ctrl);
}
public void Invoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: false));
}
public void BeginInvoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: true));
}
private void ExecuteAction(Action<Control> action) {
if (itsDispatcher.IsAlive) {
var ctrl = itsDispatcher.Target as Control;
if (ctrl != null) {
action(ctrl);
}
}
}
public static void DoInGuiThread(Control ctrl, Action action, bool forceBeginInvoke = false) {
if (ctrl.InvokeRequired) {
if (forceBeginInvoke)
ctrl.BeginInvoke(action);
else
ctrl.Invoke(action);
}
else {
action();
}
}
}
}
And initialize like this:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init(this);
...
}
And use like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
Found an even better answer without having to use a WeakReference to the form control and NO WPF References based on https://lostechies.com/gabrielschenker/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application/ and Ivan's answer above:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private SynchronizationContext itsSyncContext;
private GUIThreadDispatcher() {}
/// <summary>
/// This needs to be called on the GUI Thread somewhere
/// </summary>
public void Init() {
itsSyncContext = AsyncOperationManager.SynchronizationContext;
}
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Invoke(Action method) {
itsSyncContext.Send((state) => { method(); }, null);
}
public void BeginInvoke(Action method) {
itsSyncContext.Post((state) => { method(); }, null);
}
}
}
And initialize like this:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init();
...
}
And use like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
This turned out to be a clean implementation (relatively). Just had to include a reference to WindowsBase.dll which turns out to be a WPF library so eh..., not extremely pleased with it but it's a solution...:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private Dispatcher itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init() {
itsDispatcher = Dispatcher.CurrentDispatcher;
}
public object Invoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.Invoke(method, priority, args);
}
public DispatcherOperation BeginInvoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.BeginInvoke(method, priority, args);
}
Then initialize it like this:
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
GUIThreadDispatcher.Instance.Init(); //setup the ability to use the GUI Thread when needed via a static reference
Application.Run(new MainForm());
}
}
And then use it like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)
I have a C# WPF application built with Visual Studio 2015. I'm using MVVM and the Observer Pattern.
My Provider is a user control called 'ucClientFilter1ViewModel' that contains two text box controls where the user can search for a client(s):
namespace NSUCClientControls
{
public class ucClientFilter1ViewModel : ViewModelBase, IObservable<ClientFilterParameter>
{
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
private List<IObserver<ClientFilterParameter>> observers;
public ucClientFilter1ViewModel()
{
observers = new List<IObserver<ClientFilterParameter>>();
}
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set
{
whereSearch1 = value;
TestUpdateGrid(filterLocation);
}
}
public string WhereSearch2
{
get { return whereSearch2; }
set
{
whereSearch2 = value;
TestUpdateGrid(filterLocation);
}
}
private void TestUpdateGrid(string _filterLocation)
{
var filterInfo = new ClientFilterParameter(this);
foreach (var observer in observers)
{
observer.OnNext(filterInfo);
}
}
public IDisposable Subscribe(IObserver<ClientFilterParameter> observer)
{
// Check whether observer is already registered. If not, add it
if (!observers.Contains(observer))
{
observers.Add(observer);
// Provide observer with existing data
var filterInfo = new ClientFilterParameter(this);
observer.OnNext(filterInfo);
}
return new Unsubscriber<ClientFilterParameter>(observers, observer);
}
internal class Unsubscriber<ClientFilterParameter> : IDisposable
{
private IObserver<ClientFilterParameter> observer;
private List<IObserver<ClientFilterParameter>> observers;
public Unsubscriber(List<IObserver<ClientFilterParameter>> _observers, IObserver<ClientFilterParameter> _observer)
{
observers = _observers;
observer = _observer;
}
public void Dispose()
{
if (observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
}
}
My Observer is a user control called 'ucClientGrid1ViewModel' that contains a datagrid where the search results are displayed.
namespace NSUCClientControls
{
public class ucClientGrid1ViewModel : ViewModelBase, IObserver<ClientFilterParameter>
{
private IDisposable cancellation;
private ObservableCollection<Client> clientsMultiple;
public ucClientGrid1ViewModel()
{
}
public ObservableCollection<Client> ClientsMultiple
{
get
{
var myClientDataAccess = new ClientDataAccess();
clientsMultiple = myClientDataAccess.GetClientListFromSQL_Test2();
return clientsMultiple;
}
set
{
}
}
public virtual void Subscribe(ucClientFilter1ViewModel provider)
{
cancellation = provider.Subscribe(this);
}
public void OnNext(ClientFilterParameter myFilter)
{
OnPropertyChanged("ClientsMultiple");
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnCompleted()
{
throw new NotImplementedException();
}
}
}
This all works and I get the search results that I am expecting. But what I don't understand is why the inclusion of the following lines actually speed things up!
var myDummyWindow = new dummyWindow();
myDummyWindow.Show();
myDummyWindow.Close();
I'm new to MVVM and the observer pattern, so as I was writing the code I had included message boxes at various points to help me to follow the flow of it. It was all working as expected. Then I removed the message boxes and it still worked but the application was pausing at the end before you could continue to keep searching.
Putting a message box back in at the end prevented this pause. Replacing the message box with a "DummyWindow" that just opens and closes has the same affect and prevents the pause at the end. This is what I currently have but I'd rather not leave this in there.
Presumably opening the window causes something else to happen which stops some redundant process, and this then prevents the pause? What else could I do to prevent the pause at the end, without using this DummyWindow?
I've tried searching on here and with Bing with no luck.
Thanks in advance!
Edit:
ViewModelBase...
namespace NSCommon
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
ClientFilterParameter...
namespace NSCommon
{
public class ClientFilterParameter
{
public ClientFilterParameter(ucClientFilter1ViewModel myFilter)
{
FilterLocation = myFilter.FilterLocation;
WhereSearch1 = myFilter.WhereSearch1;
WhereSearch2 = myFilter.WhereSearch2;
}
private string filterLocation;
private string whereSearch1;
private string whereSearch2;
public string FilterLocation
{
get { return filterLocation; }
set { filterLocation = value; }
}
public string WhereSearch1
{
get { return whereSearch1; }
set { whereSearch1 = value; }
}
public string WhereSearch2
{
get { return whereSearch2; }
set { whereSearch2 = value; }
}
}
}
How do you perform databinding against the MonthCalendar.SelectionRange property? Given the property is of type 'SelectionRange' which is a class I am not sure how to go about it. Any examples would be much appreciated.
Well, there don't seem to be any obvious events for this either on the MonthCalendar or the SelectionRange, and neither implements INotifyPropertyChanged, so it looks like data-binding might not be possible here.
Update: It does, however, raise the DateChanged, so you could hook some stuff together manually, or (more usefully) by subclassing the control to expose the values and events in a fashion suitable for binding. Note the Actual(...) are useful because the end (otherwise) is just before midnight, rather than midnight itself...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;
class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
Debug.WriteLine(ToString());
}
private void SetField<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
}
}
private DateTime start, end;
public DateTime Start { get { return start; } set { SetField(ref start, value, "Start"); } }
public DateTime End { get { return end; } set { SetField(ref end, value, "End"); } }
}
class BindableCalendar : MonthCalendar
{
public DateTime ActualSelectionStart
{
get { return SelectionRange.Start; }
set { if (ActualSelectionStart != value) { SetSelectionRange(value, ActualSelectionEnd); } }
}
public DateTime ActualSelectionEnd
{
get { return SelectionRange.End; }
set { if (ActualSelectionEnd != value) { SetSelectionRange(ActualSelectionStart, value); } }
}
// should really use EventHandlerList here...
public event EventHandler ActualSelectionStartChanged, ActualSelectionEndChanged;
DateTime lastKnownStart, lastKnownEnd;
protected override void OnDateChanged(DateRangeEventArgs drevent)
{
base.OnDateChanged(drevent);
if (lastKnownStart != drevent.Start)
{
if (ActualSelectionStartChanged != null) ActualSelectionStartChanged(this, EventArgs.Empty);
lastKnownStart = drevent.Start;
}
if (lastKnownEnd != drevent.End)
{
if (ActualSelectionEndChanged != null) ActualSelectionEndChanged(this, EventArgs.Empty);
lastKnownEnd = drevent.End;
}
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
MonthCalendar cal;
Button btn;
using (Form form = new Form
{
Controls = {
(cal = new BindableCalendar { Dock = DockStyle.Fill, MaxSelectionCount = 10 }),
(btn = new Button { Dock = DockStyle.Bottom, Text = "thwack"})
}
})
{
Foo foo = new Foo { Start = DateTime.Today, End = DateTime.Today.AddDays(1) };
cal.DataBindings.Add("ActualSelectionStart", foo, "Start").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
cal.DataBindings.Add("ActualSelectionEnd", foo, "End").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
btn.Click += delegate
{
foo.Start = foo.Start.AddDays(1);
foo.End = foo.End.AddDays(1);
};
Application.Run(form);
}
}
}
For me it seems to be very simple. I just bound SelectionStart and SelectionEnd properties of the MonthCalendar component.
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionStart",
bindingSource, "DateField", true));
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionEnd",
bindingSource, "DateField", true));