C# 动态调出或切换控件的可见性
我在表单中有一个C# 动态调出或切换控件的可见性,c#,winforms,controls,C#,Winforms,Controls,我在表单中有一个TreeView,它被填充到一个groupbox中。要解决的问题是,有一个在任务上运行的操作,它从服务器应用程序加载数据。运行此操作时,TreeView的位置应显示一个进度指示器。也就是说,应该显示它而不是TreeView,并完全取代它的位置。下面是此操作的代码: private async void preload_data(object sender, System.EventArgs args) { try { // the treeView
TreeView
,它被填充到一个groupbox中。要解决的问题是,有一个在任务
上运行的操作,它从服务器应用程序加载数据。运行此操作时,TreeView
的位置应显示一个进度指示器。也就是说,应该显示它而不是TreeView
,并完全取代它的位置。下面是此操作的代码:
private async void preload_data(object sender, System.EventArgs args)
{
try
{
// the treeView should be disabled/invisible at this point
// make the CircularProgressBar enabled/visible
// get the information from the server
await Configuration.network.fetch_stuff();
}
catch (Exception ex)
{
// something bad happened
}
finally
{
// whatever happened, the treeView should be back
}
}
CircularProgressBar
(第三方控件)应如上述代码所示出现,并应替换TreeView
。它应该填充与TreeView
完全相同的空间,即dock-filled。以下是此操作的屏幕截图:
这个表单及其所有控件都是在设计器中设计的,我不想在那里做,我想用编程的方式来做。最好的办法是什么?我看过
控件.Remove()
和控件.Add()
的示例,但不清楚这是否符合此目的。使用控件的可见属性解决。使用控件的可见属性解决。在操作运行时更改视觉输出是很常见的,就像你一样。考虑禁用按钮,以阻止操作员再次按下按钮,或以视觉方式显示某些内容,以通知操作员进度
为简单起见,无需尝试捕捉
private async Task PreloadDataAsync()
{
this.ShowFetchingData(true);
// start fetching data, do not await:
var taskFetchData = Configuration.network.fetch_stuff();
// while taskFetchData not completed, await some time
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
this.ShowFetchingData(false);
}
private void ShowFetchindData(bool show)
{
// disable/enable certain buttons, menu items, show progressbar?
this.ButtonFetchData.Enabled = !show;
this.MenuFetchData.Enabled = !show;
this.ProgressBarFetchData.Visible = show;
}
private bool IsFetchingData => this.ProgressBarFetchData.Visible;
private void ShowProgress(int progress)
{
this.ProgressBarFetchData.Position = progress;
}
为简单起见,我在进度条中省略了位置检查,但您可以了解要点
用法:
private async void OnButtonFetchData(object sender, EventArgs e)
{
await this.PreloadDataAsync();
}
private async Task PreloadDataAsync()
{
// preloading should be finished within 30 seconds
// let the cancellationTokenSource request cancel after 30 seconds
TimeSpan maxPreloadTime = TimeSpan.FromSeconds(30);
using (var cancellationTokenSource = new CancellationTokenSource(maxPreloadTime))
{
await PreloadDataAsync(cancellationTokenSource.Token);
}
}
有待改进的地方
问题是根本没有超时:如果FetchStuff没有完成,您将处于无休止的等待中。microsoft提出的方法是使用CancellationToken。几乎每个异步方法都有一个带有CancellationToken的重载。考虑创建一个自己:
// existing method:
private async Task<MyData> fetch_Stuff()
{
await this.fetch_stuff(CancellationToken.None);
}
// added method with CancellationToken
private async Task<MyData> fetch_Stuff(CancellationToken token)
{
// Call async function overloads with the token,
// Regularly check if cancellation requested
while (!token.IsCancellationRequested)
{
... // fetch some more data, without waiting too long
}
}
CancellationToken的重载:
private async Task PreloadDataAsync(CancellationToken token)
{
this.ShowFetchingData(true);
// execute code similar to above, use overloads that accept token:
try
{
var taskFetchData = Configuration.network.fetch_stuff(token);
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
token.ThrowIfCancellationRequested();
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime, token);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
}
catch (TaskCancelledException exc)
{
this.ReportPreloadTimeout();
}
finally
{
this.ShowFetchingData(false);
}
}
或者,如果需要取消任务的按钮:
private CancellationTokenSource cancellationTokenSource = null;
private book IsPreloading => this.CancellationTokenSource != null;
private async Task StartStopPreload()
{
if (!this.IsPreloading)
StartPreload();
else
CancelPreload();
}
private async Task StartPreload()
{
// preload not started yet; start it without timeout;
try
{
this.cancellationTokenSource = new CancellationTokenSource();
await PreloadDataAsync(this.cancellationTokenSource.Token);
}
catch (TaskCancelledException exc)
{
this.ReportPreloadCancelled();
}
finally
{
this.cancellationTokenSource.Dispose();
this.cancellationTokenSource = null;
}
}
}
操作员可以停止预加载的方法:
private async void StopPreload()
{
this.cancellationTokenSource.Cancel();
// the method that created this source will Dispose it and assign null
}
您所要做的就是创建按钮/菜单项来启动/停止预加载在操作运行时更改视觉输出是很常见的,就像您所做的那样。考虑禁用按钮,以阻止操作员再次按下按钮,或以视觉方式显示某些内容,以通知操作员进度
为简单起见,无需尝试捕捉
private async Task PreloadDataAsync()
{
this.ShowFetchingData(true);
// start fetching data, do not await:
var taskFetchData = Configuration.network.fetch_stuff();
// while taskFetchData not completed, await some time
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
this.ShowFetchingData(false);
}
private void ShowFetchindData(bool show)
{
// disable/enable certain buttons, menu items, show progressbar?
this.ButtonFetchData.Enabled = !show;
this.MenuFetchData.Enabled = !show;
this.ProgressBarFetchData.Visible = show;
}
private bool IsFetchingData => this.ProgressBarFetchData.Visible;
private void ShowProgress(int progress)
{
this.ProgressBarFetchData.Position = progress;
}
为简单起见,我在进度条中省略了位置检查,但您可以了解要点
用法:
private async void OnButtonFetchData(object sender, EventArgs e)
{
await this.PreloadDataAsync();
}
private async Task PreloadDataAsync()
{
// preloading should be finished within 30 seconds
// let the cancellationTokenSource request cancel after 30 seconds
TimeSpan maxPreloadTime = TimeSpan.FromSeconds(30);
using (var cancellationTokenSource = new CancellationTokenSource(maxPreloadTime))
{
await PreloadDataAsync(cancellationTokenSource.Token);
}
}
有待改进的地方
问题是根本没有超时:如果FetchStuff没有完成,您将处于无休止的等待中。microsoft提出的方法是使用CancellationToken。几乎每个异步方法都有一个带有CancellationToken的重载。考虑创建一个自己:
// existing method:
private async Task<MyData> fetch_Stuff()
{
await this.fetch_stuff(CancellationToken.None);
}
// added method with CancellationToken
private async Task<MyData> fetch_Stuff(CancellationToken token)
{
// Call async function overloads with the token,
// Regularly check if cancellation requested
while (!token.IsCancellationRequested)
{
... // fetch some more data, without waiting too long
}
}
CancellationToken的重载:
private async Task PreloadDataAsync(CancellationToken token)
{
this.ShowFetchingData(true);
// execute code similar to above, use overloads that accept token:
try
{
var taskFetchData = Configuration.network.fetch_stuff(token);
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
token.ThrowIfCancellationRequested();
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime, token);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
}
catch (TaskCancelledException exc)
{
this.ReportPreloadTimeout();
}
finally
{
this.ShowFetchingData(false);
}
}
或者,如果需要取消任务的按钮:
private CancellationTokenSource cancellationTokenSource = null;
private book IsPreloading => this.CancellationTokenSource != null;
private async Task StartStopPreload()
{
if (!this.IsPreloading)
StartPreload();
else
CancelPreload();
}
private async Task StartPreload()
{
// preload not started yet; start it without timeout;
try
{
this.cancellationTokenSource = new CancellationTokenSource();
await PreloadDataAsync(this.cancellationTokenSource.Token);
}
catch (TaskCancelledException exc)
{
this.ReportPreloadCancelled();
}
finally
{
this.cancellationTokenSource.Dispose();
this.cancellationTokenSource = null;
}
}
}
操作员可以停止预加载的方法:
private async void StopPreload()
{
this.cancellationTokenSource.Cancel();
// the method that created this source will Dispose it and assign null
}
您所要做的就是创建按钮/菜单项来启动/停止预加载,那么这里有什么问题?首先忘记删除然后重新添加TreeView。隐藏TreeView并在wait
行之前显示进度,然后在finally
块中反转进度?@JQSOFT是的,我同时玩过它,它似乎起作用了。但是,我已经意识到,调整窗口大小时,不应该对CircularProgressBar
进行停靠填充或调整大小,因为它会扭曲。如何将其居中放置在TreeView
所在的空间中?例如:progress.Location=新点((itsParent.Width-progress.Width)/2,(itsParentHeight-progress.Height)/2)
或将进度集中在设计器中,并将其锚定
属性设置为无
。那么这里有什么问题?首先忘记删除然后重新添加TreeView。隐藏TreeView并在wait
行之前显示进度,然后在finally
块中反转进度?@JQSOFT是的,我同时玩过它,它似乎起作用了。但是,我已经意识到,调整窗口大小时,不应该对CircularProgressBar
进行停靠填充或调整大小,因为它会扭曲。如何将其居中放置在TreeView
所在的空间中?例如:progress.Location=新点((itsParent.Width-progress.Width)/2,(itsParentHeight-progress.Height)/2)
或将进度集中在设计器中,并将其锚定
属性设置为无
。