动态换出或切换控件的可见性

Posted

技术标签:

【中文标题】动态换出或切换控件的可见性【英文标题】:Dynamically swapping out or toggling visibility of controls 【发布时间】:2020-09-20 11:35:00 【问题描述】:

我在一个表单中有一个TreeView,它被停靠在一个组框中。要解决的问题是,有一个在Task 上运行的操作,它从服务器应用程序加载数据。当它运行时,应该在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 完全相同的空间,它是停靠填充的。下面是这个截图:

这个表单及其所有控件都是在设计器中设计的,我不想在那里做,我想以编程方式做。解决此问题的最佳方法是什么?我查看了Controls.Remove()Controls.Add() 的示例,但不清楚这是否符合此目的。

【问题讨论】:

那么这里有什么问题呢?首先忘记删除然后重新添加 TreeView。隐藏 TreeView 并在 await 行之前显示进度,然后在 finally 块中反转它? @JQSOFT 是的,我在此期间玩过它,它似乎工作。但是,我已经意识到 CircularProgressBar 在调整窗口大小时不应填充或调整大小,因为它会扭曲。如何在TreeView 所在的空间内居中? 点赞:progress.Location = new Point((itsParent.Width - progress.Width) / 2, (itsParentHeight - progress.Height) / 2); 或者将进度居中于设计器并将其Anchor 属性设置为None 【参考方案1】:

在动作运行时改变视觉输出是很常见的,就像你做的那样。考虑禁用按钮,以阻止操作员再次按下按钮,或以视觉方式显示某些内容,以告知操作员进度。

为简单起见,没有 try-catch

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();

改进空间

这样做的问题是根本没有超时:如果 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
    

考虑抛出异常而不是 IsCancellationRequested:ThrowIfCancellationRequested。

用法:

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);
    

带有 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

您所要做的就是创建按钮/菜单项来开始/停止预加载

【讨论】:

【参考方案2】:

使用控件的Visible 属性解决。

【讨论】:

以上是关于动态换出或切换控件的可见性的主要内容,如果未能解决你的问题,请参考以下文章

动态切换资源列可见性

基于控件可见性在运行时自定义动态布局

动态切换表行的可见性 - bootstrap-vue

覆盖 2 个控件并使用 WPF 切换哪一个可见

JQuery在多个图像的鼠标悬停时切换可见性

XAML ContentControl 不改变可见性