admin管理员组文章数量:1304921
In a C# WinForms application, before the main view is displayed, I need to show a wait box dialog with a progress bar set to Marquee mode (where the blocks move from left to right).
While the animation is running, I need to execute an async method that connects to a service and retrieves some data. Once the data is fetched, the wait box should close.
I've completed this task and will share my results, but I feel the implementation is messy. I’d prefer a more modern approach instead of using BackgroundWorker or manually handling events.
I'm currently using .NET 4.8. My main question is: How can I perform this task while keeping the message pump active and ensuring the animation remains smooth?
try
{
WaitBoxForm.ShowWaitBox();
var signal = new ManualResetEvent(false);
var task = Task.Run(async () =>
{
apiAppKey = await GetLoginData();
signal.Set();
});
while (!signal.WaitOne(TimeSpan.FromMilliseconds(1)))
{
Application.DoEvents();
}
}
catch (Exception ex)
{
WaitBoxForm.CloseWaitBox();
MessageService.Information(apiExceptionMessage(ex.Message));
return false;
}
finally
{
WaitBoxForm.CloseWaitBox();
}
As requested:
public partial class WaitBoxForm : Form
{
private static WaitBoxForm waitBoxForm;
public WaitBoxForm()
{
InitializeComponent();
}
public static void ShowWaitBox()
{
if (waitBoxForm != null) return;
waitBoxForm = new WaitBoxForm();
Task.Run(() => Application.Run(waitBoxForm));
}
public static void CloseWaitBox()
{
if (waitBoxForm != null)
{
waitBoxForm.Invoke((Action)(() => waitBoxForm.Close()));
waitBoxForm = null;
}
}
}
In a C# WinForms application, before the main view is displayed, I need to show a wait box dialog with a progress bar set to Marquee mode (where the blocks move from left to right).
While the animation is running, I need to execute an async method that connects to a service and retrieves some data. Once the data is fetched, the wait box should close.
I've completed this task and will share my results, but I feel the implementation is messy. I’d prefer a more modern approach instead of using BackgroundWorker or manually handling events.
I'm currently using .NET 4.8. My main question is: How can I perform this task while keeping the message pump active and ensuring the animation remains smooth?
try
{
WaitBoxForm.ShowWaitBox();
var signal = new ManualResetEvent(false);
var task = Task.Run(async () =>
{
apiAppKey = await GetLoginData();
signal.Set();
});
while (!signal.WaitOne(TimeSpan.FromMilliseconds(1)))
{
Application.DoEvents();
}
}
catch (Exception ex)
{
WaitBoxForm.CloseWaitBox();
MessageService.Information(apiExceptionMessage(ex.Message));
return false;
}
finally
{
WaitBoxForm.CloseWaitBox();
}
As requested:
public partial class WaitBoxForm : Form
{
private static WaitBoxForm waitBoxForm;
public WaitBoxForm()
{
InitializeComponent();
}
public static void ShowWaitBox()
{
if (waitBoxForm != null) return;
waitBoxForm = new WaitBoxForm();
Task.Run(() => Application.Run(waitBoxForm));
}
public static void CloseWaitBox()
{
if (waitBoxForm != null)
{
waitBoxForm.Invoke((Action)(() => waitBoxForm.Close()));
waitBoxForm = null;
}
}
}
Share
Improve this question
edited Feb 4 at 7:57
John
asked Feb 4 at 7:39
JohnJohn
1,9155 gold badges34 silver badges62 bronze badges
10
|
Show 5 more comments
2 Answers
Reset to default 1One idea is to launch the Task
that retrieves the data immediately after the application starts, before showing any UI to the user, and then await
this task in the Load
or Shown
event handler of the WaitBoxForm
. The example below starts two consecutive message loops on the same thread (the UI thread), one for the WaitBoxForm
and later another one for the MainForm
. The retrieved data are stored inside the task (it's a Task<TResult>
).
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Task<Data> task = Task.Run(async () =>
{
// Execute an async method that connects to a service and retrieves some data.
return data;
});
var waitBox = new WaitBoxForm();
waitBox.Shown += async (sender, e) =>
{
await task;
await Task.Yield(); // Might not be nesessary
waitBox.Close();
};
Application.Run(waitBoxForm); // Start first message loop
if (!task.IsCompletedSuccessfully) return;
Application.Run(new MainForm(task.Result)); // Start second message loop
}
It is assumed that the MainForm
has a constructor with a single parameter, which represents the data retrieved from the service.
The purpose of the await Task.Yield();
is to ensure that the Close
will be called asynchronously. I know that some Form
events, like the Closing
, throw exceptions if you call the Close
method inside the handler. I don't know if the Load
/Shown
are among these events. The above code has not been tested.
The key to making this scheme behave is to make sure MainForm.Handle
is the first window created (because the OS typically considers the first visible top-level window to be the primary UI window for the process). Ordinarily, the Handle
is created when the form is shown. But in this case, we want to show the WaitBox
(and its asynchronous ProgressBar
) first. Here's one way to make this work:
- Force the main for window creation using
_ = Handle;
- Override
SetVisibleCore
and preventMainForm
from becoming visible until we're ready. - Using
BeginInvoke
, post a message at the tail of the message queue to show the wait box.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
_ = Handle;
BeginInvoke(new Action(() => ConnectToService()));
// Setup the DataGridView
Load += (sender, e) => dataGridView.DataSource = Responses;
}
protected override void SetVisibleCore(bool value) =>
base.SetVisibleCore(value && _initialized);
bool _initialized = false;
IList Responses { get; } = new BindingList<ReceivedHttpResponseEventArgs>();
private void ConnectToService()
{
using (var waitBox = new WaitBox())
{
waitBox.ResponseReceived += (sender, e) =>
{
Debug.Assert(
!InvokeRequired,
"Expecting that we are ALREADY ON the UI thread");
Responses.Add(e);
};
waitBox.ShowDialog();
}
_initialized = true;
Show();
}
}
WaitBox Minimal Example
This demo uses the https://catfact.ninja API as a stand-in for "an async method that connects to a service and retrieves some data". The received "facts" are used to populate the data source of a DataGridView
.
public partial class WaitBox : Form
{
public WaitBox()
{
InitializeComponent();
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.None;
progressBar.Style = ProgressBarStyle.Marquee;
progressBar.MarqueeAnimationSpeed = 50;
}
protected async override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
if (Visible)
{
labelProgress.Text = "Connecting to service...";
// Includes some cosmetic delay for demo purposes
for (int i = 0; i < 10; i++)
{
labelProgress.Text = await GetCatFactAsync();
await Task.Delay(TimeSpan.FromSeconds(1));
}
DialogResult = DialogResult.OK;
}
}
HttpClient _httpClient = new HttpClient();
private string _nextPageUrl = "https://catfact.ninja/facts?limit=1";
private async Task<string> GetCatFactAsync()
{
try
{
HttpResponseMessage response = await _httpClient.GetAsync(_nextPageUrl);
if (response.IsSuccessStatusCode)
{
string jsonData = await response.Content.ReadAsStringAsync();
var catFacts = JsonConvert.DeserializeObject<ResponseParser>(jsonData);
if (catFacts?.Data != null && catFacts.Data.Count > 0)
{
_nextPageUrl = $"{catFacts.Next_Page_Url}&limit=1";
ResponseReceived?.Invoke(this, catFacts.Data[0]);
return catFacts.Data[0].Fact;
}
}
}
catch (Exception ex) { Debug.WriteLine($"Error: {ex.Message}"); }
return null;
}
public event EventHandler<ReceivedHttpResponseEventArgs> ResponseReceived;
}
class ResponseParser
{
[JsonProperty("data")]
public List<ReceivedHttpResponseEventArgs> Data { get; set; }
[JsonProperty("next_page_url")]
public string Next_Page_Url { get; set; }
}
public class ReceivedHttpResponseEventArgs : EventArgs
{
[JsonProperty("fact")]
public string Fact { get; set; }
}
本文标签:
版权声明:本文标题:c# - Running async method with open form (wait box dialog) and animation (progress bar with marque style) - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741782284a2397380.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
Main
entry point of the application? – Theodor Zoulias Commented Feb 4 at 7:51Application.DoEvents();
- if you see this, something is probably going very wrong. What are you trying to do, at all? Some sort of progress dialog while doing background processing? – Fildor Commented Feb 4 at 8:35BackgroundWorker
toasync
/await
, you could take a look at this answer. – Theodor Zoulias Commented Feb 4 at 9:38