深夜风尘 發表於 2020-12-24 10:52:00

[C#] (原创)进度等待窗口(附:自定义控件的使用)

<h1>一、前言</h1>
<p>技术没有先进与落后,只有合适与不合适。</p>
<p>在程序当中,经常有耗时较长的操作,为了给用户更好的体验,就需要给用户一个及时的反馈,这种时候就需要用到进度等待窗口。</p>
<p>实现进度等待窗口的技术有很多,比如:BackgroundWorker、Thread等。</p>
<p>不过技术不是难点,难点在于怎么使等待窗口美观实用。所以本文中就基于前几篇的自定义控件:LProgressBar和LLabel,去实现进度等窗口。</p>
<p>相关文章:</p>
<p> (原创)一步一步教你自定义控件——04,ProgressBar(进度条)</p>
<p> (原创)一步一步教你自定义控件——05,Label(原生控件)&nbsp;&nbsp;</p>
<p>&nbsp;</p>
<p><strong>相信看完的你,一定会有所收获。</strong></p>
<p>本文地址:https://www.cnblogs.com/lesliexin/p/14121618.html</p>
<hr>
<h1>&nbsp;</h1>
<h1>二、前期分析</h1>
<h2>(一)为什么需要进度等窗口?</h2>
<p>为了在执行耗时操作时,给用户更好的、更直观的体验。</p>
<h2>(二)预期功能效果</h2>
<h3>1,功能</h3>
<p>(1),支持“取消”操作,当然也支持“不能取消”操作。</p>
<p>(2),支持进度明确与进度不明确时显示不同样式。</p>
<p>(3),支持明细进度。</p>
<h3>2,效果</h3>
<p>Win7:</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223100431082-1633294919.gif" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223100442520-90525396.gif" alt="" loading="lazy"></p>
<p>Win10:</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223100459709-1131038425.gif" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223100511188-740080981.gif" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<hr>
<p>&nbsp;</p>
<h1>三、开始实现</h1>
<h2>(一)布局窗体</h2>
<h3>&nbsp;1,新建窗体</h3>
<p>(1),在工程上右击,选择“添加”-&gt;“窗体(Windows 窗体)”,命名为:LProgress.cs。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223102228505-78114308.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>(2),修改窗体相关属性</p>
<p>Font:微软雅黑,9pt。比之默认的“宋体,9pt”的效果更加美观。</p>
<p>BackColor:White。更加美观,特别是在Win10上。</p>
<p>ForeColor:Black。为了防止被某些系统主题影响而显示的不是黑色。</p>
<p>FormBorderStyle:FixedDialog。使用户不可调用窗口尺寸。</p>
<p>MaximizeBox:False。不显示最大化按钮。</p>
<p>MinimizeBox:False。不显示最小化按钮。</p>
<p>ShowIcon:False。不显示窗口图标。</p>
<p>ShowInTaskbar:False。不在任务栏上显示图标。</p>
<p>TopMost:True。置顶显示窗口。</p>
<p>&nbsp;</p>
<h3>2,添加控件</h3>
<p>这里需要说明一下,在添加控件时,如果如本文这样窗体与自定义控件工程在同一个解决方案中,那么在工具栏的最上方会自动显示当前工具中的自定义控件,选中即可使用。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223105129581-24324161.jpg" alt="" loading="lazy"></p>
<p>如果是窗体与自定义控件工程不在同一个解决方案中,比如引用的是自定义控件的DLL文件,那么就需要将自定义控件DLL拖到工具栏上,此时工具栏上就会显示出里面的自定义控件。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223105210581-498482994.gif" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>(1)总体控件布局</h4>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223110753099-1410951570.jpg" alt="" loading="lazy"></p>
<h4>(2)lPBar_Main(主进度)</h4>
<p>控件:LProgressBar</p>
<p>关键属性:</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223111033443-2091899397.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>(3)lPBar_Child(子进度)</h4>
<p>控件:LProgressBar</p>
<p>关键属性:</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223111111872-399926180.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>(4)lbl_Main(主进度文本)</h4>
<p>控件:LLabel</p>
<p>关键属性:</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223111330317-350610621.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>(5)lbl_Child(子进度文本)</h4>
<p>控件:LLabel</p>
<p>关键属性:</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223111337361-520295751.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>(6)btn_Cancel(取消按钮)</h4>
<p>控件:Button</p>
<p>关键属性:</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223111354783-2113064788.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>(7)相关说明</h4>
<p>&nbsp;这里说下lbl_Main和lbl_Child为什么不使用原生Label控件,而使用自定义LLabel控件。</p>
<p>因为在实际运行时,窗口大小是固定不变的,所以如果内容过多时,如果使用原生Label,就会在鼠标移上去后显示悬浮提示,对整体外观有所影响。如果内容变化快的话,悬浮提示也会频繁显示,但提示的文本却早已过去,没有提示的意义。</p>
<p>所以此处使用了基于原生控件改造的自定义控件:LLabel。(详见: (原创)一步一步教你自定义控件——05,Label(原生控件)&nbsp; )</p>
<p>这样通过属性”L_EnableAutoTip=False“,就可以不再显示悬浮提示。</p>
<p>&nbsp;</p>
<h2>&nbsp;(二)添加属性</h2>
<h4>1,是否显示“取消”按钮</h4>
<p>对于耗时操作,有的时候是可以让用户取消的;而有的时候,则是不能让用户取消。所以需要一个属性去控制是否显示“取消”按钮。</p>
<p>同时,为了美观,在不显示“取消”按钮时,调整窗口高度到合适位置。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223131529480-458516830.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>2,主进度条模式(进度已知/进度未知)</h4>
<p>一些操作的进度是已知的,比如下载进度、复制进度;而有一些操作的的进度是未知的,比如查询操作、调用其他耗时任务等。</p>
<p>针对进度已知还是进度未知,则进度条的样式也需要有相应的改变。</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223131547884-1351252995.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>3,窗口标题</h4>
<p>当前进度等待窗口的标题</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223131554374-1990521757.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>4,默认显示的主进度文本</h4>
<p>默认的主进度文本</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223131601460-571140079.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>5,是否显示子进度条</h4>
<p>因为大部分时候只需要一个进度,所以子进度条默认是不显示的。而且,子进度条的作用决定了子进度条本身是可以灵活显示/不显示的。</p>
<p>本属性只是提供一个初始化的状态。</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223131610274-1096886791.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>6,自定义参数</h4>
<p>因为进度等待窗口是一个单独的窗体,而耗时操作也是在本窗口中执行,所以就需要进行数据的交互,而此参数即是为了进行数据的交互。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223131619366-1717416981.jpg" alt="" loading="lazy">&nbsp;</p>
<p>&nbsp;</p>
<h2>(三)添加构造函数</h2>
<p>为了方便调用,为窗口增加一带参数的构造函数。</p>
<p>这里给大家一个小技巧,在添加构造函数时,可以借用VS的“快速操作和重构...”功能来快速生成构造函数。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223132703528-2146876334.gif" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h2>(四)添加事件</h2>
<p>&nbsp;本篇中执行耗时操作所采用的方法是开一新线程去执行,此方法非常简单且使用方便,所以需要添加一个事件,以让调用者在事件中执行耗时任务。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223133058087-751034846.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h2>(五)添加公共方法</h2>
<p>因为采用的是线程+事件,所以需要开放一些公共方法,以供调用者在事件实现中使用。</p>
<h4>1,更新主进度文本</h4>
<p>此方法用于更新主进度文本的内容。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223133456426-1612188342.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>(1),在方法中判断本次更新文本是否与当前显示文本是否一致:if(sMsg!=sLastMainMsg,是为了减少不必要的赋值,减少闪烁,提高性能。(下同将不再赘述)</p>
<p>(2),因为是在新线程中操作控件,所以如果直接操作控件的话,比如给控件赋值,将会提示“线程间操作无效。从不是创建控件'XXX'的线程访问它”的错误,这种情况下需要通过委托的方式去去操作控件。(当然也可以使用不安全代码去解除这种限制,但不推荐使用。)(下同将不再赘述)</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223141633837-1250781915.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>2,更新主进度条进度值</h4>
<p>此方法用于在主进度条是进度已知时,更新主进度条的进度。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143112683-254472993.jpg" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143119837-963675020.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>3,更新子进度文本</h4>
<p>此方法用于更新子进度文本。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143132667-494515802.jpg" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143140302-1006854951.jpg" alt="" loading="lazy"></p>
<h4>4,更新子进度条进度值</h4>
<p>此方法用于在子进度条是进度已知时,更新子进度条的进度。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143150506-547802374.jpg" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143158363-376771602.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>5,改变子进度条显示状态</h4>
<p>因为子进度条是灵活显示的,所以提供本方法去改变进度条的显示状态。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143208921-61660375.jpg" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143217174-1304012350.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>6,改变子进度条样式(进度已知/进度未知)</h4>
<p>因为子进度条是灵活显示的,所以提供本方法去改变进度条的样式,是进度已知时样式还是进度未知时样式。</p>
<p>当是进度未知时,要自动启动进度条动画。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143227811-997558697.jpg" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143235589-1297524907.jpg" alt="" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143250134-1902187882.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h4>7,退出</h4>
<p>在事件中实现操作时,可以使用本方法手动结束等待窗口。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143336429-1240916187.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<h2>(六)事件实现</h2>
<h4>1,窗口加载事件</h4>
<p>在窗口加载时,我们要满足以下条件:</p>
<p>(1),为了更加美观,所以我们不会隐藏掉“关闭”按钮,但是“关闭”按钮会和“取消”按钮功能相冲突,特别是在不能取消时。</p>
<p>所以我们要将“关闭”按钮无效化。主要使用到了系统API:GetSystemMenu和RemoveMenu</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223143835816-553538332.jpg" alt="" loading="lazy"></p>
<p>(2),如果是进度未知时,要令主进度条开始动画。</p>
<p>(3),开启新线程。此线程即是用来执行耗时任务的,所以要在此线程方法中触发事件委托,以供调用者实现。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223144241067-656540508.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>所以,加载的实现如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223144301093-1077425962.jpg" alt="" loading="lazy"></p>
<h4>2,“取消”按钮事件</h4>
<p>点击“取消”时,要满足以下几个条件:</p>
<p>(1),如果主进度条是进度未知,则停止进度条动画。</p>
<p>(2),安全退出。</p>
<p>&nbsp;</p>
<p>所以,“取消”按钮的实现如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223144421134-210848923.jpg" alt="" loading="lazy"></p>
<hr>
<p>&nbsp;</p>
<h1>四、效果演示</h1>
<p>(1),新建窗体,添加LSwitchButton、Button、Label,如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223144705551-1701322594.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>(2),在“进度窗口”点击事件中调用进度等待窗口。</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223145247444-1356762279.jpg" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>(3),实现耗时任务</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223145317973-336159148.jpg" alt="" loading="lazy"></p>
<p>(4),运行</p>
<p><img src="https://img2020.cnblogs.com/blog/1686429/202012/1686429-20201223145853446-474921853.gif" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<hr>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h1>五、结束语</h1>
<p>本篇所实现的进度等待窗口,技术上很简单,但在美观上、功能上并不弱,而且使用起来也简单。作为日常使用也是足够的。</p>
<p>本篇文章的目的更多的是为了给大家一个使用自定义控件的例子,毕竟自定义控件要在实际的应用中才能体现出价值。</p>
<p>不要被常规思维所束缚,相信自己所掌握的知识。</p>
<p>&nbsp;</p>
<hr>
<p>&nbsp;</p>
<h1>六、源代码及工程下载</h1>
<p>https://files.cnblogs.com/files/lesliexin/05,LProgress.7z</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/lesliexin/p/14121618.html
頁: [1]
查看完整版本: [C#] (原创)进度等待窗口(附:自定义控件的使用)