Winforms C#: async initialize - winforms

I have a Winform C# application, that has a DataGridView. I intend to load data from Azure table storage into it meanwhile the form is initializing. I'm afraid if I load so much data from Azure table storage, my app will be crashed. Can I use Async load data from Azure table storage in form constructor?

Where you'd typically do this kind of work is in the Form_Load event. For example:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void Form1_Load(object sender, EventArgs e)
{
await RefreshDataAsync();
}
private async void button1_Click(object sender, EventArgs e)
{
await RefreshDataAsync();
}
private async Task RefreshDataAsync()
{
button1.Enabled = false;
listBox1.Items.Clear();
try
{
var data = await GetDataFromDataSourceAsync();
foreach(var item in data)
{
listBox1.Items.Add(item);
}
}
finally
{
button1.Enabled = true;
}
}
}
}
So you'd create a "refresh data" method, and have your Form_Load event call that. Since you segregated the refresh code, you could have other things call it as well, such as a button.
WinForm events such as Form_Load or button click events can be made asynchronous by simply adding the async keyword to them. More info about that here.

Related

Datagrid remains empty after asynchronous initialization in view model constructor

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.

What is the correct way to update controls on main UI thread

In my C# winforms application I am loading the data from the database using Task in the background, below is my code. But after uploading the data I am updating my UI control(s) which is BindingSource. This BindingSource component is connected to the DataGrid which will be updated as well.
what I need to know is that what I am doing is the right way or is there another better way to achieve the same goal.
private async void Form_Load(object sender, EventArgs e)
{
Task loadDataTask = Task.Factory.StartNew(() =>
{
// LoadData(); // loading the data from the database
});
await loadDataTask.ConfigureAwait(true);
FormBindingSource.DataSource = _businessObjecsCollection;
}
if you can afford it, make method which loads data asynchronous. then you will be able to change Form method as:
private async void Form_Load(object sender, EventArgs e)
{
await LoadDataAsync();
FormBindingSource.DataSource = _businessObjecsCollection;
}
maybe even
private async void Form_Load(object sender, EventArgs e)
{
FormBindingSource.DataSource = await LoadDataAsync();
}
otherwise current method seems fine

Visual Studio 2012 Windows Form application

I am working on Windows Form application on Visual Studio 2012. I have 2 forms.
Add_Item_to_DB1
Add_Item_to_DB2
Both of these forms call a third form called SUBMIT. Now, based on where this form is being called from, it has to submit information to a different database. Everything else in the SUBMIT form is EXACTLY the same except, data is inserted to a different database.
Is there a way to find out where the form is being called from? Kinda new to Form applications.
Thank you
If you open the SUBMIT form with the ShowDialog() method you will be able to determine the form that opened the SUBMIT form via the Owner property. For example:
public partial class Add_Owner_To_Db_1 : Form
{
private void button1_Click(object sender, EventArgs e)
{
var submitForm = new SUBMIT();
submitForm.ShowDialog(this);
}
}
public partial class SUBMIT : Form
{
private void SUBMIT_Load(object sender, EventArgs e)
{
//label1.Text will equal "Add_Owner_To_Db_1"
label1.Text = this.Owner.Text;
}
}
Alternatively you can expose a public property on your SUBMIT form that can be populated from the parent form. For example:
public partial class Add_Owner_To_Db_1 : Form
{
private void button1_Click(object sender, EventArgs e)
{
var submitForm = new SUBMIT();
submitForm.ParentName = "Add_Owner_To_Db_1";
submitForm.Show();
}
}
public partial class SUBMIT : Form
{
public string ParentName { get; set; }
private void SUBMIT_Load(object sender, EventArgs e)
{
//label1.Text will equal "Add_Owner_To_Db_1"
label1.Text = ParentName;
}
}
HTH

Winforms: Best way to keep winforms app unlocked?

I have a winforms app, that's locking up during a web service request
I've tried using doEvents to keep the app unlocked, but its still not responsive enough,
How can I get around this locking, so that the app is responsive at all times?
The best way is simply to do your IO work on another thread, perhaps via BackgroundWorker, or the async methods of WebClient.
See perhaps here. Be sure to use Invoke when talking back to the UI controls (thread affinity); full example:
using System;
using System.Net;
using System.Windows.Forms;
class MyForm : Form
{
Button btn;
TextBox txt;
WebClient client;
public MyForm()
{
btn = new Button();
btn.Text = "Download";
txt = new TextBox();
txt.Multiline = true;
txt.Dock = DockStyle.Right;
Controls.Add(btn);
Controls.Add(txt);
btn.Click += new EventHandler(btn_Click);
client = new WebClient();
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
}
void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
Invoke((MethodInvoker)delegate
{
if (e.Cancelled) txt.Text = "Cancelled";
else if (e.Error != null) txt.Text = e.Error.Message;
else txt.Text = e.Result;
});
}
void btn_Click(object sender, EventArgs e)
{
client.DownloadStringAsync(new Uri("http://google.com"));
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
}
Do the web service request in a background thread. Be cautious of too many calls to Application.DoEvents().

Windows Forms Binding Problem: How to know if user has changed a value

I currently have a windows form application composed of a textbox and two buttons (previous and next) The textbox is bound to the name of a Person on a list. Previous and Next button changes the position of the BindingManager by 1 (either increment or decrement).
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;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private List<Person> stringList;
BindingManagerBase bindingManager;
public Form1()
{
InitializeComponent();
stringList = new List<Person>();
stringList.Add(new Person{ name = "person1" });
stringList.Add(new Person { name = "person2" });
stringList.Add(new Person { name = "person3" });
stringList.Add(new Person { name = "person4" });
bindingManager = this.BindingContext[stringList];
bindingManager.CurrentChanged += handleCurrentChanged;
textBox1.DataBindings.Add(new Binding("Text", stringList, "name"));
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
private void handleCurrentChanged(object sender, EventArgs e)
{
MessageBox.Show("handleCurrentChanged");
}
private void button1_Click(object sender, EventArgs e)
{
bindingManager.Position++;
}
private void button2_Click(object sender, EventArgs e)
{
bindingManager.Position--;
}
}
public class Person
{
public String name { get; set; }
}
}
What is needed is to prompt the user whenever he/she presses the previous or next button whether to save his changes or not. But only if he made some changes to the textbox.
My problem is how to know whether there has been some changes to the Person object so that I can initiate the prompt. I had intended to use BindingManagerBase's currentChanged event but this only checks if you have changed which item you are working on the list. I also can't check for the textbox since the previous and next button manipulate it also, which I do not want to prompt the user for.
You could implement INotifyPropertyChanged interface in your person class and then your GUI class can be notified every time a property changed in your Person class.

Resources