Datagrid remains empty after asynchronous initialization in view model constructor - wpf

I have a WPF application with a view containing a data grid and a view model with an observable collection that is initialized by calling an asynchronous method in the constructor. But the data grid remains empty upon running the code.
The view model class looks like this.
internal class MainWindowViewModel : INotifyPropertyChanged
{
private readonly IBookingRecordService service;
public event PropertyChangedEventHandler? PropertyChanged;
private ObservableCollection<BookingRecord> bookingRecords = new();
public ObservableCollection<BookingRecord> BookingRecords
{
get => bookingRecords;
set
{
bookingRecords = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingRecords)));
}
}
public MainWindowViewModel()
{
service = new BookingRecordService();
Task.Run(() => LoadBookingRecords());
}
private async Task LoadBookingRecords()
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}
}
I all LoadBookingRecords() in the constructor, so that the data starts loading on initialization of the view model already but I do it asynchronously, so it does not block the UI thread and makes the application unresponsive.
I have tried waiting for the completion of the task in the constructor via
Task.Run(() => LoadBookingRecords()).Wait();
to check that this has something to do with the asynchronous function call. And indeed, if I wait for the method to finish in the constructor, the data grid displays correctly. But I don't want to wait for the task to finish on the UI thread because it blocks the UI.
I have read that you must raise the PropertyChanged event on the UI thread to trigger a UI update and I suppose that is the problem here. I have also read that one can use
Application.Current.Dispatcher.BeginInvoke()
to schedule a delegate to run on the UI thread as soon as possible, so I tried the following.
private async Task LoadBookingRecords()
{
await Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}));
}
But this leaves the DataGrid empty as well.

"'asynchronous ... in constructor" is something you must avoid.
Async calls must be awaited, which can not be done in a constructor. Declare an awaitable Initialize method instead
public Task Initialize()
{
return LoadBookingRecords();
}
and call it in an async Loaded event handler in your MainWindow:
private static async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await viewModel.Initialize();
}
Alternatively, create a factory method like
public static async Task<MainWindowViewModel> Create()
{
var viewModel = new MainWindowViewModel();
await viewModel.LoadBookingRecords();
return viewModel;
}
and call that in the Loaded handler:
private static async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
DataContext = await MainWindowViewModel.Create();
}

Building on Clemens' answer, I tried something a little different in order to avoid touching the MainWindow code-behind.
I removed the call on LoadBookingRecords in the constructor and instead created a delegate command as a property that holds this method.
internal class MainWindowViewModel : INotifyPropertyChanged
{
private readonly IBookingRecordService service;
private ObservableCollection<BookingRecord> bookingRecords = new();
public ICommand LoadBookingRecordsCommand { get; set; }
public ObservableCollection<BookingRecord> BookingRecords
{
get => bookingRecords;
set
{
bookingRecords = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BookingRecords)));
}
}
public MainWindowViewModel()
{
service = new BookingRecordService();
LoadBookingRecordsCommand = new DelegateCommand(async _ => await LoadBookingRecords());
}
private async Task LoadBookingRecords()
{
BookingRecords = new ObservableCollection<BookingRecord>(await service.Get());
}
}
I then added the NuGet package Microsoft.Xaml.Behaviors.Wpf to the project and added the following namespace to the MainWindow XAML.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
Finally, I bound the delegate command to the MainWindow's Loaded event.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadBookingRecordsCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Now the data grid displays correctly after being loaded.

Related

Prism, IConfirmNavigationRequest, InteractionRequest and Async

I have an issue, i coded my view, viewmodel creation into the ModuleInit.Initialize method
this.container.RegisterType<IControlPanel, ViewModels.SeveritiesViewModel>("SeveritiesViewModel");
this.container.RegisterType<object, Views.SeveritiesView>("SeveritiesView", new InjectionConstructor(new ResolvedParameter<IControlPanel>("SeveritiesViewModel")));
SeveritiesVeiwModel inherits from ViewModelBase
public class ViewModelBase : BindableBase, IControlPanel, INavigationAware, IConfirmNavigationRequest
Constructor for ViewModelBase calls two virtual methods. Initialize and GetData.
GetData performs some data access methods using async await.
so the problem i have is Prism constructs my SeveritiesViewModel, the GetData method runs, and throws and exception which i catch. i would then like to display a dialog using the InteractionRequest, however the view.DataContext has not yet be set, hence no bindings or Interaction.Triggers to receive the InteractionRequest.
so i thought i should look into RegionManager.RequestNaviagte using a callback. i thought since all my viewmodels implement IConfirmNavigationRequest i could return false in the NavigationResult from the View/viewmodel being injected. however ConfirmNavigationRequest is never called. this is wpf not silverlight?
so how do i work this extremely decoupled application. do i need to implement some type of shared service?
I guess i am going to need to store exceptions until the view has finished binding with the viewmodel, perhaps implement my own interface with a method to check an exceptions collection and in the view call the interface method?
why is ConfirmNavigationRequest never called?
InteractionRequest work great after the DataContext is set, but before; i'm at a loss.
Any advise would be appreciated.
Thanks
Gary
here is some code.
toolbar button command click runs the following.
this.regionManager.RequestNavigate("ContentRegion", "SeveritiesView");
here is the code behind for the view.
public partial class SeveritiesView : UserControl, IApplicationView
{
public SeveritiesView(IControlPanel model)
{
InitializeComponent();
this.DataContext = model;
}
public string ViewName
{
get { return "SeveritiesView"; }
}
}
ViewModelBase.
protected ViewModelBase(bool initializeDB = true)
{
notifications = new List<NotificationWindowNotification>();
this.uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
NotificationRequest = new InteractionRequest<NotificationWindowNotification>();
ConfirmationRequest = new InteractionRequest<ConfirmationWindowNotification>();
if (initializeDB)
{
EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
entityBuilder.ProviderConnectionString = EventLogAnalysis.Properties.Settings.Default.ConnectionString;
db = new ServerEventLogEntities(entityBuilder.ToString());
}
ThrobberVisible = Visibility.Visible;
Initialize();
GetData();
}
SeveritiesViewModel.
public SeveritiesViewModel(IRegionManager regionManager, IEventAggregator eventAggregator) : base()
{
try
{
this.regionManager = regionManager;
this.eventAggregator = eventAggregator;
eventAggregator.GetEvent<AddSeverity>().Subscribe(AddSeverity);
eventAggregator.GetEvent<DeleteSeverity>().Subscribe(DeleteSeverity);
}
catch(Exception e)
{
uiFactory.StartNew(() =>
NotificationRequest.Raise(new NotificationWindowNotification()
{
Title = string.Format("Error during {0}.{1}"
, ModuleName, System.Reflection.MethodBase.GetCurrentMethod().Name),
Content = string.Format("{0}", e.Message)
})
).Wait();
}
}
protected async override void GetData()
{
try
{
List<Task> tasks = new List<Task>();
tasks.Add(GetEventFilterSeverities());
await Task.WhenAll(tasks).ContinueWith((t) =>
{
ThrobberVisible = Visibility.Collapsed;
eventAggregator.GetEvent<RecordStatusEvent>().Publish(new RecordStatusMessage() { CanAdd = true, CanDelete =(currentEventFilterSeverity != null), IsClosing = false });
}
, TaskScheduler.FromCurrentSynchronizationContext());
}
catch(Exception e)
{
notifications.Add(new NotificationWindowNotification()
{
Title = string.Format("Error during {0}.{1}"
, ModuleName, System.Reflection.MethodBase.GetCurrentMethod().Name),
Content = string.Format("{0}", e.Message)
});
}
}
protected async Task GetEventFilterSeverities()
{
try
{
throw new NullReferenceException("My exception");
ObservableCollection<EventFilterSeverity> _eventFilterSeverities = new ObservableCollection<EventFilterSeverity>();
var eventFilterSeverities = await (from sg in db.EventFilterSeverities
orderby sg.EventFilterSeverityID
select sg).ToListAsync();
foreach (EventFilterSeverity efs in eventFilterSeverities)
_eventFilterSeverities.Add(efs);
EventFilterSeverities = _eventFilterSeverities;
}
catch(Exception e)
{
notifications.Add(new NotificationWindowNotification()
{
Title = string.Format("Error during {0}.{1}"
, ModuleName, System.Reflection.MethodBase.GetCurrentMethod().Name),
Content = string.Format("{0}", e.Message)
});
}
}
Two fairly easy solutions;
Do not start data access until the Shell has been displayed and interactions are possible
Catch the exception and immediatly await on a Task that completes when the interaction request becomes available. Is this when the navigation completes? This effectively queues the interaction for when it can be displayed.
This looks promising.
in the view
<i:Interaction.Triggers>
<i:EventTrigger EventName="Raised" SourceObject="{Binding NotificationRequest}">
<i:EventTrigger.Actions>
<dialogs:PopupWindowAction IsModal="True"/>
</i:EventTrigger.Actions>
</i:EventTrigger>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="DisplayPreBoundExceptions"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In the ViewModelBase
public void DisplayPreBoundExceptions()
{
notifications.ForEach((t) => NotificationRequest.Raise(t));
notifications.Clear();
}

Execute Method from Codehind using ViewModel WPF

I've abandoned the MVVM midway through app development just to get this app out.
I've written a method in the code behind to update the database/datagrid etc.
My application navigation is using Commands to the ViewModel firing some event but never touches the code-behind except one time to initialize the class.
So basically I push the button one time and it works with the default initial setting but I can't call my code-behind Update() method anymore once the view as been intialized.
How can I call this code-behind method from the view model?
Thanks!!
Update code
//Navigation ViewModel
//PaneVm.cs
public CommandExtension NewAssignmentCommand { get; set; }
private void CreateCommands()
{
NewAssignmentCommand = new CommandExtension(NewAssignment, CanNewAssignment);
}
GlobalCommands.NewAssignmentCommand = NewAssignmentCommand;
private bool CanNewGroupAssignment(object obj)
{
return true;
}
private void NewGroupAssignment(object obj)
{
OnPropertyChanged("NewGroupAssignmentCommand");
}
//MainVM.cs
// [Events]
void _PaneVm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "NewGroupAssignmentCommand")
WorkspaceVm.CurrentVm = new NewAssignmentsVm();
}
//NewAssignmentVm.cs
//Constructor
public NewAssignmentsVm()
{
var rc = new RepositoryContext();
_RoResearchers = new ObservableCollection<Researcher>(rc.ResearcherData.GetAllResearchers());
_QuarterDateTime = DateTime.Now;
CreateCommands();
}
//NewAssignment.cs
//Code-behind
//The method
private void UpdateGrid()
{
report_datagrid.ItemsSource = null;
using (var rc = new RepositoryContext())
{
if (quarter_datepicker.SelectedDate != null)
{
if (!String.IsNullOrEmpty(reportType))
researchers = rc.ResearcherData.GetResearchersWeeksByQuarter(Convert.ToDateTime(quarter_datepicker.SelectedDate), reportType).ToList();
}
}
}
UPDATE 2:
I solved my problem based off this answer. I created a Global Action
public static class GlobalCommands
{
public static Action UpdateGrid { get; set; }
}
Then in my code-behind constructor I set the value public
MyCodeBehind()
{
GlobalCommands.UpdateGrid = new Action(() => this.UpdateGrid());
}
Didn't need to bind to the context again. Everything else was the same. Thank you
Main idea is:
class MyCodeBehind
{
public MyCodeBehind()
{
Action action = new Action(()=> this.SomeMethodIWantToCall());
var myVM = new MyVM(action); // This is your ViewModel
this.DataContext = myVM;
}
private void SomeMethodIWantToCall(){...}
}
class MyVM
{
private Action action;
public MyVM(Action someAction)
{
this.action = someAction;
}
private void SomeMethodInVM()
{
this.action(); // Calls the method SomeMethodIWantToCall() in your code behind
}
}
Instead of letting code-behind know about viewmodel, You can make use of NotifyOnSourceUpdated in your xaml binding.
Something like this:
<TextBlock Grid.Row="1" Grid.Column="1" Name="RentText"
Text="{Binding Path=Rent, Mode=OneWay, NotifyOnTargetUpdated=True}"
TargetUpdated="OnTargetUpdated"/>
Here, 'OnTargetUpdated' is a handler in your code behind. This handler will be invoked when "Rent" property of ViewModel is changed.
Details at MSDN

making async calls using mvvm in silverlight

I am trying to make a call to a wcf service with my silverlight application and I am having some trouble understanding how the model returns the result back to the view model. Within my view model I have the following command:
public DelegateCommand GetSearchResultCommand
{
get
{
if (this._getSearchResultCommand == null)
this._getSearchResultCommand = new DelegateCommand(GetSearchResultCommandExecute, CanGetSearchResultsCommandExecute);
return this._getSearchResultCommand;
}
}
private void GetSearchResultCommandExecute(object parameter)
{
this.SearchResults = this._DataModel.GetSearchResults(this.SearchTerm);
}
/// <summary>
/// Bindable property for SearchResults
/// </summary>
public ObservableCollection<QueryResponse> SearchResults
{
get
{
return this._SearchResults;
}
private set
{
if (this._SearchResults == value)
return;
// Set the new value and notify
this._SearchResults = value;
this.NotifyPropertyChanged("SearchResults");
}
}
then within my model I have the following code
public ObservableCollection<QueryResponse> GetSearchResults(string searchQuery)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += new EventHandler<QueryCompletedEventArgs>(sc_QueryCompleted);
sc.QueryAsync(new Query { QueryText = searchQuery });
return LastSearchResults;
}
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
this.LastSearchResults = results;
}
When I insert breakpoints within the model I see where the query is being executed and a result is returned within the model (this.LastSearchResults = results) however I cannot seem to get this collection to update/ notify the view model of the result. I've generated and run a similar test using just a method and dummy class and it seems to work so I suspect the issue is due to the async call /threading. I have INotifyPropertyChanged within the ViewModel to sync the View and ViewModel. Do I need to also implement INotifyPropChng within the model as well? I'm new to mvvm so any help / example of how I should approach this would be appreciated.
Thank you,
UPDATE
In further testing I added INotifyPropertyChanged to the model and changed the Completed event as follows:
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
//this.LastSearchResults = results;
SearchResults = results;
}
Placing a watch on Search Results I now see it is updated with results from teh WCF. My question is still around is this teh correct approach? It seems to work right now however I am curious if I am missing something else or if I should not be placing INotify within the Model.
Thank you,
I've found that it's best to encapsulate my WCF services in an additional layer of Service classes. This allows me to more easily Unit Test my ViewModels. There are several patterns when doing this, though this is the simplest I've used. The pattern is to create a method that matches the definition of the service call, though also contains an Action that can be invoked after the service call completes.
public class Service : IService
{
public void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += (s,e) =>
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
reply(results);
};
sc.QueryAsync(new Query { QueryText = searchQuery });
}
}
You can also provide an interface that your ViewModel can use. This makes Unit Testing even easier, though is optional.
public interface IService
{
void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply);
}
Your ViewModel would then look something like this:
public class MyViewModel : INotifyPropertyChanged
{
private IService _service;
public MyViewModel()
: this(new Service())
{ }
public MyViewModel(IService service)
{
_service = service;
SearchResults = new ObservableCollection<QueryResponse>();
}
private ObservableCollection<QueryResponse> _searchResults
public ObservableCollection<QueryResponse> SearchResults
{
get { return _searchResults; }
set
{
_searchResults = value;
NotifyPropertyChanged("SearchResults");
}
}
public void Search()
{
_service.GetSearchResults("abcd", results =>
{
SearchResults.AddRange(results);
});
}
protected void NotifyPropertyChanged(string property)
{
var handler = this.PropertyChanged;
if(handler != null)
handler(new PropertyChangedEventArgs(property));
}
}
An additional reason for encapsulating your service calls into another class like this is that it can provide a single place for such things as logging and error handling. That way your ViewModel itself doesn't need to take care of those things specifically related to the Service.
I would likely use something along the lines of:
public class ViewModel : INotifyPropertyChanged
{
private readonly IModel model;
private readonly DelegateCommand getSearchResultsCommand;
public DelegateCommand GetSearchResultsCommand
{
get { return getSearchResultsCommand; }
}
public ObservableCollection<QueryResponse> SearchResults
{
get { return model.SearchResults; }
}
public ViewModel(IModel model)
{
this.model = model;
this.model.SearchResultsRetrieved += new EventHandler(model_SearchResultsRetrieved);
this.getSearchResultsCommand = new DelegateCommand(model.GetSearchResultCommandExecute, model.CanGetSearchResultsCommandExecute);
}
private void model_SearchResultsRetrieved(object sender, EventArgs e)
{
this.NotifyPropertyChanged("SearchResults");
}
}
public interface IModel
{
event EventHandler SearchResultsRetrieved;
void GetSearchResultCommandExecute(object parameter);
bool CanGetSearchResultsCommandExecute(object parameter);
ObservableCollection<QueryResponse> SearchResults { get; }
}
With the SearchResultsRetrieved event being fired by the Model when its SearchResults collection has been filled with the appropriate data. I prefer to have custom events rather than implement INotifyPropertyChanged on my models, particularly if there are only one, or a few, events that need to be communicated to the viewmodel.

Silverlight 4.0 Call Long Running Method Asynchronously (MVVM)

My Silverlight MVVM App's ViewModel Properties that are poupulated from a service library (not WCF Service).
The library method does some operation that may take some time to complete. After the operation is completed the return value is assigned to the ViewModel property.
Since the View is bound to the ViewModel property it would refresh automatically when the Property Value changes. But, during the operation as expected the UI becomes non responsive because its synchronous operation.
How to do the following operation Asynchronously?
Service Contract & Implementation:
public class ItemsLoadedEventArgs : EventArgs
{
public List<string> Items { get; set; }
}
public interface ISomeService
{
event EventHandler<ItemsLoadedEventArgs> GetItemsCompleted;
void GetItemsAsync();
}
public class SomeService : ISomeService
{
public event EventHandler<ItemsLoadedEventArgs> GetItemsCompleted;
public void GetItemsAsync()
{
// do something long here
// how to do this long running operation Asynchronously?
// and then notify the subscriber of the Event?
// when the operation is completed fire the event
if(this.GetItemsCompleted != null)
{
this.GetItemsCompleted(this, new ItemsLoadedEventArgs { Items = resulthere });
}
}
}
ViewModel:
public class HomeViewModel : ViewModel
{
private ISomeService service;
private ObservableCollection<string> _items;
// Items property is bound to UI
public ObservableCollection<string> Items
{
get { return this._items; }
set
{
this._items = value;
this.RaisePropertyChanged(() => this.Items);
}
}
// DI
public HomeViewModel(ISomeService service)
{
...
this.service = service;
// load items
this.LoadItems();
}
private void LoadItems()
{
this.service.GetItemsCompleted += (s, ea) =>
{
this.Items = new ObservableCollection<string>(ea.Items);
};
this.service.GetItemsAsync();
}
}
Problem:
Since the data is loaded in the constructor, and the operation is synchronous it makes the UI unresponsive.
How to perform the operation inside the GetItemsAsync method of the SomeService class Asynchronously?
Use Dispatcher.BeginInvoke to call your library method. You have to go back to the UI thread to update your ViewModel when it is done. See this question for info on this.

Silverlight DataBinding cross thread issue

I have an Image control with it's source bound to a property on an object(string url to an image). After making a service call, i update the data object with a new URL. The exception is thrown after it leaves my code, after invoking the PropertyChanged event.
The data structure and the service logic are all done in a core dll that has no knowledge of the UI. How do I sync up with the UI thread when i cant access a Dispatcher?
PS: Accessing Application.Current.RootVisual in order to get at a Dispatcher is not a solution because the root visual is on a different thread(causing the exact exception i need to prevent).
PPS: This only is a problem with the image control, binding to any other ui element, the cross thread issue is handled for you.
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Also look here.
Have you tried implementing INotifyPropertyChanged?
The property getter for RootVisual on the Application class has a thread check which causes that exception. I got around this by storing the root visual's dispatcher in my own property in my App.xaml.cs:
public static Dispatcher RootVisualDispatcher { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
RootVisualDispatcher = RootVisual.Dispatcher;
}
If you then call BeginInvoke on App.RootVisualDispatcher rather than Application.Current.RootVisual.Dispatcher you shouldn't get this exception.
I ran into a similar issue to this, but this was in windows forms:
I have a class that has it's own thread, updating statistics about another process, there is a control in my UI that is databound to this object. I was running into cross-thread call issues, here is how I resolved it:
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
if(m_MainWindow.InvokeRequired)
m_MainWindow.Invoke(
PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
This seems to work great, if anyone has suggestions, please let me know.
When ever we want to update UI related items that action should happen in the UI thread else you will get an invalid cross thread access exception
Deployment.Current.Dispatcher.BeginInvoke( () =>
{
UpdateUI(); // DO the actions in the function Update UI
});
public void UpdateUI()
{
//to do :Update UI elements here
}
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer type
// in the list does.
public partial class Form1 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Click +=
new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form1_Load(object sender, EventArgs e)
{
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer> customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}

Resources