用于监控UI的Datagrid模板






4.67/5 (3投票s)
这是一个在构建用于模拟和表格数据呈现的监控UI中使用数据网格的模板或模式。
引言
数据网格最常见的用途之一是监控。 这种监控屏幕通常需要跟踪计划作业,查看模拟结果,分析表格数据,如股票等。虽然还有其他可能的方法,但我认为这种方法很好,因此想分享它。
在多线程环境中使用数据网格很棘手。 让我们首先迭代这些问题。
- 将有一个后台线程来处理数据,并根据此处理的结果更新数据网格。
- WPF中的UI控件只能由UI线程更新。
- 可以有多个UI控件一起工作以实现整体功能(进度条,按钮等)。 程序员常常错误地以不安全的方式传递控件引用。
- 绑定到
datagrid
的数据源不应被任何其他类修改。
模板
我不太喜欢datatable
和dataset
,因为它们不提供强类型检查。 我更喜欢绑定到datamodel
对象。 这是一个粗略的UML图以供入门。
在这里,我们开始逐个定义所有组件。
定义DataModel
创建一个数据持有者类作为数据模型。 为了使其可自动更新,它必须实现INotifyPropertyChanged
。 这是一个示例实现。
public class NetworkDetails : INotifyPropertyChanged
{
public string IpAddress { get; set; }
private bool m_IsMonitoring = false;
public bool IsMonitoring
{
get { return m_IsMonitoring; }
set
{
if (m_IsMonitoring == value)
return;
m_IsMonitoring = value;
OnPropertyChanged("IsMonitoring");
}
}
private int m_LoadFactor = 0;
public int LoadFactor
{
get { return m_LoadFactor; }
set
{
if (m_LoadFactor == value)
return;
m_LoadFactor = value;
OnPropertyChanged("LoadFactor");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
定义GridViewManager
此类将封装整体网格操作并与其他控件协调。 此类将执行一些常见操作
- 管理后台工作者(
start
、stop
、consume
事件) - 启动数据处理操作
- 将处理后的数据更新到
datamodel
中,由于数据模型实现了INotifyPropertyChanged
,更改将反映在datagrid
中。 - 更新其他UI控件,如进度条或按钮。
public class GridViewManager
{
public ObservableCollection<NetworkDetails> lstNetworkDetails =
new ObservableCollection<NetworkDetails>();
BackgroundWorker m_bgworker = new BackgroundWorker();
MainWindow.GridViewManagerContext m_context = null;
public GridViewManager(MainWindow.GridViewManagerContext sgmc)
{
m_context = sgmc;
m_bgworker.WorkerReportsProgress = true;
m_bgworker.DoWork += worker_DoWork;
m_bgworker.RunWorkerCompleted += bgworker_RunWorkerCompleted;
m_bgworker.ProgressChanged += bgworker_ProgressChanged;
}
void bgworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{ … }
void bgworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{ … }
void worker_DoWork(object sender, DoWorkEventArgs e)
{ … }
public void Start()
{ … }
public void Stop()
{ … }
}
控制GridViewManager的创建
GridViewManager
类保存将绑定到gridview
的itemsource
的集合。 它基本上是主窗口的一个从属类,并且它也将大量使用其他WPF控件来实现整体功能。 此类必须在UIThread
附近使用。 它应该理想地是一个嵌套类。 然而,这可能会使代码过于混乱。 如果我们可以将gridviewmanager
类的实例化限制为主窗口,那么它将解决问题。 如果是C++,我们可以利用friend
函数。 然而,在C#中,我们可以使用一些替代机制来限制实例化。 如下所示
- 创建另一个类来将所有UI控件组合在一起。 让我们称它为
GridViewManagerContext
。 - 仅在
GridViewManager
中定义一个构造函数,该构造函数期望一个GridViewManagerContext
对象。 通过这种方式,GridViewManager
只能使用GridViewManagerContext
对象创建。 - 将
GridViewManagerContext
的构造函数设为private
。 这样,任何人都无法实例化它。 - 在主窗口对象中定义一个嵌套的
private
接口,用于创建GridViewManagerContext
的实例。 - 在
GridViewManager
内部创建嵌套的工厂类以创建GridViewManagerContext
的实例。 在factory
类中显式地实现嵌套的private
接口。 - 这种花招有什么用? 你会意识到现在
GridViewManager
类只能被MainWindow
对象实例化。
可能有其他方法可以实现代码安全性,但是,我认为这个方法更简单。 这是一个精简到清晰的示例代码。 请注意,这些是父窗口对象中的嵌套类。
private interface IPrivateFactory
{
GridViewManagerContext CreateInstance();
}
public sealed class GridViewManagerContext
{
private GridViewManagerContext()
{ … }
public class GridViewManagerContextFactory : IPrivateFactory
{
GridViewManagerContext IPrivateFactory.CreateInstance()
{
return new GridViewManagerContext();
}
}
}
//Then the constructor of parent window can hold this code.
public MainWindow()
{
InitializeComponent();
IPrivateFactory factory = new GridViewManagerContext.GridViewManagerContextFactory();
GridViewManagerContext context = factory.CreateInstance();
sgm = new GridViewManager(context);
dgTest1.ItemsSource = sgm.lstNetworkDetails;
}
请参考附带的zip文件以获得完整的代码清晰度。
关注点
我在多个项目中使用了这个模板,发现这是最简单的遵循方法。 还有其他替代方案,但每一个都有一些漏洞。 在一个单独的类中处理控件应该小心,这是大多数代码失败的地方。