- 图1 -工作演示 *
我正在运行一个winforms应用程序,并且实现了一个状态/进度TreeView。它可以显示一组(可能是分层的)要完成的任务的状态(通过图标)和进度。我的问题不是关于如何实现TreeView控件本身。我已经涵盖了这一部分。TreeView只是后台工作向用户显示状态/进度的方式。
我有一组方法,我想在主UI线程之外运行。它们需要按顺序运行。它们是一个更大进程中的步骤。我可以将它们组织成层次结构;这将是一个很好的树结构。
这些方法中的每一个都将由树中的一个节点表示。我可能是从旧的Sql Server DTS状态面板中得到这种可视化方法的想法的。我仍然喜欢这个想法。
我想知道每个方法的完成时间和结果,可能还有一些文本状态。我还想知道一个通用的机制,可以用来冒泡进度。我将使用这些来在TreeView中与该方法对应的节点上创建一个所有者绘制的进度条。
我读过一些关于多线程和Task类的书,但并不完全理解它。我不在乎解决方案是否使用它。但也许那样会更优雅,我不知道。它看起来比回调更直接,但也许你知道得更清楚。
任务类:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Serialization;
using System.Linq;
namespace DeveloperWorkbench.Nodes
{
public class Task : INode
{
public delegate void TaskStatusDelegate(Task sender, TaskStatus taskStatus);
public event ProgressDelegate ProgressChanged;
public event StatusDelegate Status;
public event TaskStatusDelegate TaskStatusChanged;
public Task()
{
_children = new List<Task>();
}
[XmlIgnore()]
public bool CanHaveChildren { get; private set; }
private List<Task> _children;
public List<Task> Children
{
get
{
_children.ForEach(x => x.Parent = this);
return _children;
}
set
{
_children = value;
_children.ForEach(x => x.Parent = this);
}
}
[XmlIgnore()]
public List<string> ChildTypes { get; private set; }
public string FullName { get; set; }
private float _maxProgress = 0;
[Browsable(false)]
[XmlIgnore()]
public float MaxProgress
{
get { return _maxProgress; }
set
{
_maxProgress = value;
RaiseProgress(this, Progress, MaxProgress);
}
}
private Delegate _method;
[Browsable(false)]
[XmlIgnore()]
public Delegate Method
{
get { return _method; }
set
{
if (_method == value) return;
_method = value;
Name = Method.Method.Name;
TypeName = Method.Method.ReflectedType.FullName;
}
}
private string _name;
[ReadOnly(true)]
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
//Method = GetMethodByName(???, _name);
FullName = ProperCaseToSpaces(_name);
}
}
[Browsable(false)]
[XmlIgnore()]
public INode Parent { get; set; }
private float _progress = 0;
[Browsable(false)]
[XmlIgnore()]
public float Progress
{
get { return _progress; }
set
{
_progress = value;
RaiseProgress(this, Progress, MaxProgress);
}
}
public List<KeyValuePair<string, object>> RelatedItems { get; set; }
private TaskStatus _taskStatus = TaskStatus.Created;
[Browsable(false)]
[XmlIgnore()]
public TaskStatus TaskStatus
{
get { return _taskStatus; }
set
{
_taskStatus = value;
TaskStatusChanged(this, _taskStatus);
}
}
[ReadOnly(true)]
public string TypeName { get; set; }
public bool Visited { get; set; }
public Task Add(Task child)
{
Children.Add(child);
child.Parent = this;
child.ProgressChanged += Child_Progress;
return child;
}
private void Done(System.Threading.Tasks.Task task)
{
TaskStatus = TaskStatus.RanToCompletion;
}
public void Execute()
{
Progress = 0;
TaskStatus = TaskStatus.Running;
var systemTask = new System.Threading.Tasks.Task((Action)Method);
systemTask.ContinueWith(Done);
systemTask.Start();
if (Parent != null)
systemTask.Wait();
}
private static string ProperCaseToSpaces(string text)
{
return Regex.Replace(text, @"(\B[A-Z]+?(?=[A-Z][^A-Z])|\B[A-Z]+?(?=[^A-Z]))", " $1");
}
public void RaiseProgress(INode sender, float progress = 0, float maxProgress = 100)
{
ProgressChanged(sender, progress, maxProgress);
}
public void RaiseStatus(string status = "Ready")
{
Status(status);
}
public void Refresh(bool force)
{
throw new NotImplementedException();
}
public void RefreshChildren(bool force, string childType = null)
{
throw new NotImplementedException();
}
public List<KeyValuePair<string, INode>> RefreshRelatedItems(bool force)
{
throw new NotImplementedException();
}
//Usage: myTask.SetMethod(() => MyMethod(0, 40));
public void SetMethod(Action method)
{
Method = method;
}
//Usage: myTask.SetMethod(() => MyFunction(myArgument));
public void SetMethod<T>(Func<T> function)
{
Method = function;
}
public void SetMethod(Object target)
{
if (target.GetType().FullName == TypeName)
Method = GetMethodByName(target, Name);
else
{
var name = Name;
SetMethod(() => FakeExecute(this));
Name = name;
TypeName = null;
}
foreach (var child in Children)
{
child.SetMethod(target);
}
}
public void Child_Progress(INode sender, float progress = 0, float maxProgress = 100)
{
MaxProgress = _children.Sum(x => x.MaxProgress);
Progress = _children.Sum(x => x.Progress);
}
public static Task Create<T>(Func<T> method, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(method);
return task;
}
public static Task Create(Action method, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(method);
return task;
}
public static Task Create(string methodName, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(() => FakeExecute(task));
task.Name = methodName;
task.TypeName = null;
return task;
}
private static Task InnerCreate(Task parent)
{
var task = new Task();
if (parent != null)
parent.Add(task);
return task;
}
public static Task CurrentTask(Task rootTask, int stackFrame = 1)
{
var taskMethodName = new StackFrame(stackFrame).GetMethod().Name;
return Find(rootTask, taskMethodName);
}
private static void FakeExecute(Task task)
{
foreach (Task child in task.Children)
{
child.MaxProgress = 100;
child.Progress = 0;
child.TaskStatus = TaskStatus.WaitingToRun;
}
foreach (Task child in task.Children)
{
child.Execute();
}
}
private static Task Find(Task task, string methodName)
{
return task.Method.Method.Name == methodName ?
task :
task.Children.Select(child => Find(child, methodName)).FirstOrDefault(found => found != null);
}
static Delegate GetMethodByName(object target, string methodName)
{
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
MethodInfo method = target.GetType().GetMethod(methodName, bindingFlags);
return method.ReturnType == typeof(void) ? Delegate.CreateDelegate(typeof(Action), target, method) : null;
}
}
}
StatusList类别:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using Retalix.R10.DeveloperWorkbench.Nodes;
using Retalix.R10.DeveloperWorkbench.UI.Helpers;
using Task = Retalix.R10.DeveloperWorkbench.Nodes.Task;
namespace DeveloperWorkbench.UI.Controls
{
public partial class StatusList : UserControl
{
// Import the SetWindowRgn function from the user32.DLL
// From the Unmanaged Code
[DllImport("user32.DLL", EntryPoint = "SetWindowRgn")]
private static extern int SetWindowRgn(int hWnd, int hRgn, int bRedraw);
[System.Runtime.InteropServices.DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern System.IntPtr CreateRoundRectRgn
(
int nLeftRect, // x-coordinate of upper-left corner
int nTopRect, // y-coordinate of upper-left corner
int nRightRect, // x-coordinate of lower-right corner
int nBottomRect, // y-coordinate of lower-right corner
int nWidthEllipse, // height of ellipse
int nHeightEllipse // width of ellipse
);
[System.Runtime.InteropServices.DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
private static extern bool DeleteObject(System.IntPtr hObject);
public StatusList()
{
InitializeComponent();
}
private TreeNode Add(TreeNodeCollection nodes, string text, string imageKey, object tag)
{
var treeNode = nodes.Add(tag.GetHashCode().ToString(), text);
treeNode.Tag = tag;
treeNode.ImageKey = imageKey;
treeNode.SelectedImageKey = imageKey;
tvTreeView.ExpandAll();
return treeNode;
}
public TreeNode Add(Task task)
{
var nodes = tvTreeView.Nodes;
if (task.Parent != null)
nodes = Find(task.Parent).Nodes;
task.TaskStatusChanged += Task_TaskStatusChanged;
task.ProgressChanged += Task_Progress;
var treeNode = Add(nodes, task.FullName, StatusIcon(task.TaskStatus), task);
foreach(var child in task.Children)
{
Add(child);
}
return treeNode;
}
private TreeNode Find(object tag)
{
var treeNodes = tvTreeView.Nodes.Find(tag.GetHashCode().ToString(), true);
if (treeNodes.Length > 0)
return treeNodes[0];
return null;
}
private string StatusIcon(System.Threading.Tasks.TaskStatus status)
{
switch (status)
{
case TaskStatus.Canceled:
case TaskStatus.Created:
case TaskStatus.Faulted:
case TaskStatus.RanToCompletion:
return status.ToString();
break;
case TaskStatus.Running:
case TaskStatus.WaitingForChildrenToComplete:
return TaskStatus.Running.ToString();
break;
default:
if (status.ToString().StartsWith("Waiting"))
return "Waiting";
break;
}
return "Created";
}
private void tvTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
var task = (Task) e.Node.Tag;
if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
//e.Graphics.DrawRectangle(SystemPens.ControlDark, e.Bounds.Left, e.Bounds.Top , e.Bounds.Width - 1, e.Bounds.Height - 1);
}
if(task.TaskStatus == TaskStatus.Running)
{
var borderBrush = new LinearGradientBrush(new Point(e.Bounds.Left + 1, e.Bounds.Top + 3), new Point(e.Bounds.Left + 1, e.Bounds.Bottom), Color.White, Color.FromArgb(200, Color.LightGray));
var borderRectangle = new Rectangle(e.Bounds.Left + 1, e.Bounds.Top + 3, e.Bounds.Width - 10, e.Bounds.Height - 6);
var borderGraphicsPath = RoundedRectangle.Create(borderRectangle);
e.Graphics.FillPath(borderBrush, borderGraphicsPath);
e.Graphics.DrawPath(Pens.DarkGray, borderGraphicsPath);
//e.Graphics.FillRectangle(borderBrush, borderRectangle);
//e.Graphics.DrawRectangle(pen, borderRectangle);
if (task.Progress > 0)
{
//pen.DashStyle = DashStyle.Dot;
var width = (task.Progress / task.MaxProgress) * (e.Bounds.Width - 11);
var progressRectangle = new Rectangle(e.Bounds.Left + 2, e.Bounds.Top + 4, (int)width, e.Bounds.Height - 7);
var progressGraphicsPath = RoundedRectangle.Create(progressRectangle, 5, RoundedRectangle.RectangleCorners.TopLeft | RoundedRectangle.RectangleCorners.BottomLeft);
//e.Graphics.DrawRectangle(pen, rectangle);
var progressBrush = new LinearGradientBrush(new Point(progressRectangle.Left, progressRectangle.Top - 1), new Point(progressRectangle.Left, progressRectangle.Bottom), Color.White, Color.LimeGreen);
e.Graphics.FillPath(progressBrush, progressGraphicsPath);
//e.Graphics.FillRectangle(progressLinearGradientBrush, progressRectangle);
//GraphicsPath path = RoundedRectangle.Create(rectangle);
//e.Graphics.DrawPath(Pens.Black, path);
//System.IntPtr ptrBorder = CreateRoundRectRgn(e.Bounds.Left, e.Bounds.Top, e.Bounds.Left + 50, e.Bounds.Bottom, 5, 5);
//try { SetWindowRgn(tvTreeView.Handle.ToInt32(), ptrBorder.ToInt32(), 1) ; }
//finally { DeleteObject(ptrBorder); }
}
}
var textSize = e.Graphics.MeasureString(task.Name, tvTreeView.Font);
var controlText = SystemBrushes.ControlText;
e.Graphics.DrawString(task.Name, tvTreeView.Font, controlText, e.Bounds.Left - 1, e.Bounds.Top + e.Bounds.Height / 2f - textSize.Height / 2f);
//if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
// controlText = SystemBrushes.HighlightText;
}
public void Task_Progress(Nodes.INode sender, float progress = 0, float maxProgress = 100)
{
if (IsDisposed) return;
if (InvokeRequired)
{
Invoke(new ProgressDelegate(Task_Progress), sender, progress, maxProgress);
}
else
{
if (tvTreeView.IsDisposed) return;
var treeNode = Find(sender);
if (treeNode != null)
{
tvTreeView.Invalidate(treeNode.Bounds);
}
}
}
public void Task_TaskStatusChanged(Task sender, TaskStatus taskStatus)
{
if (IsDisposed) return;
if (InvokeRequired)
{
Invoke(new Task.TaskStatusDelegate(Task_TaskStatusChanged), sender, taskStatus);
}
else
{
if (tvTreeView.IsDisposed) return;
var treeNode = Find(sender);
if (treeNode != null)
{
treeNode.ImageKey = StatusIcon(taskStatus);
treeNode.SelectedImageKey = treeNode.ImageKey;
}
}
}
}
}
以及如何使用:
using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Task = Retalix.R10.DeveloperWorkbench.Nodes.Task;
namespace DeveloperWorkbench.UI
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
BuildTaskHierarchy();
}
private Task _rootTask;
public void BuildTaskHierarchy()
{
var roottaskXml = @"c:\temp\roottask.xml";
if (File.Exists(roottaskXml))
{
//method hierarchy can be deserialized...
_rootTask = (Task)Serialization.Deserialize(typeof(Task), roottaskXml);
_rootTask.SetMethod(target: this);
}
else
{
//...or constructed from scratch
_rootTask = Task.Create("Avert War With The Klingons");
Task.Create(GetToTheEnterprise, _rootTask);
var taskC = Task.Create("Kill General Chang", _rootTask);
Task.Create(FindThatThingsTailpipe, taskC);
Task.Create(TargetThatExplosionAndFire, taskC);
Task.Create(ThwartCampKhitomerAssassination, _rootTask);
Task.Create(ExplainToHighCommand, _rootTask);
Serialization.Serialize(_rootTask, roottaskXml);
}
statusList1.Add(_rootTask);
}
private void GetToTheEnterprise()
{
LongOp();
}
private void FindThatThingsTailpipe()
{
LongOp();
}
private void TargetThatExplosionAndFire()
{
LongOp();
}
private void ThwartCampKhitomerAssassination()
{
LongOp();
}
private void ExplainToHighCommand()
{
LongOp();
}
private void LongOp()
{
var task = Task.CurrentTask(_rootTask, 2);
task.MaxProgress = 100;
for (var i = 0; i <= 50; i++)
{
task.Progress = i*2;
Thread.Sleep(25);
}
}
private void button1_Click(object sender, EventArgs e)
{
_rootTask.Execute();
}
}
}
我只是在发布我的进度。我已经在我的实际应用程序中测试过了,它是有效的。我仍然需要一个方便的函数来提高任何方法的进度。我仍然在寻找关于如何减少这里所需的工具的反馈。我想要尽可能少的侵入性策略。在运行时监视调用链的东西将是一个很棒的补充。
3条答案
按热度按时间sg24os4d1#
Progress
类使得用进度更新UI变得非常容易。只需在UI中创建一个progress示例;它可以获取后台进程当前拥有的任何信息,并相应地更新UI:
您可以根据自己的情况进行调整。假定后台进程将具有某种类型来表示操作,您可以将其Map回表示该操作的树节点。您还可以传递更新UI所需的任何其他信息。如果您有大量信息要传递,请考虑创建一个新的命名类型来表示它。而不是像这里一样使用
Tuple
。然后将进度传递给后台进程,不管它是什么(它可能是一个新线程、一个任务、一个异步方法的回调,或者其他什么)。
如果您没有.NET 4.5,则可以相当轻松地创建自己的此类版本:
eh57zj3b2#
阅读BackgroundWorker类,它是一种古老但非常简单的方法,可以在不涉及线程复杂性的情况下完成后台工作。
您所要做的就是创建一个,处理它的DoWork事件来执行您的逻辑(它将在后台运行),并通过它的ReportProgress函数将进度传递回主ui线程,然后您将处理该函数来更新树的ui。
beq87vna3#
更新UI的最佳选择是将同步责任留给.NET本身。利用
async
和await
,在Task
完成时帮助从后台线程切换到UI线程。This library解决了您所需的确切目的。
看看WPF和Blazor here的例子。
NUGET包here。