设备分析和报告工具 (DART)






4.78/5 (4投票s)
一个开源的 WPF 工具,
- 下载源代码 - 1.09 MB
- 从 http://dart.codeplex.com/SourceControl/list/changesets 下载最新版本源代码
- 安装 Dart - http://dart.codeplex.com/releases/
关于
DART 是一款用于分析大容量网络驱动器和文件夹的系统信息和内存分布的工具。它提供系统内存的图形化分析,以及使用历史记录、快照和报告,使设备维护变得轻松高效。

引言
DART 是一款基于 WPF 的工具。其架构完全基于 MVVM 模式。DART 大量使用了松耦合代码、模块单元测试、视图与应用程序逻辑的分离以及非阻塞异步调用。它使用 Visual Studio 2010 开发。
背景
这个想法最初来源于我用两个包含超过 1500 GB 电影的外部硬盘驱动器时内存不足。然后我不得不逐个分析每个文件夹,检查它包含多少文件以及占用了多少内存。我需要的是一个文件夹及其子文件夹的图形化表示,以及方便地查看其详细信息。现有的 XP 和 Windows 7 无法提供这些详细信息。因此 DART 应运而生。
目标
本文的目的是提供 DART 的高级架构。文章通过代码片段解释了实现核心功能的关键部分。建议具备 WPF 和 MVVM 的先验知识以便理解下面的讨论。如需更详细的阐述或解释特定项目,请联系我或访问论坛 - http://dart.codeplex.com/discussions。
第一部分 - 框架
框架负责收集 DART 所需的信息。
Analyzer
Attribute
类是用于收集系统信息的占位符。它有一个 `GetDetails()` 方法,该方法根据元素是驱动器、目录还是文件,返回一个包含该元素详细信息的字典。
public Dictionary<string,string> GetDetails()
{
try
{
Dictionary<string, string> Details = new Dictionary<string, string>();
if (Type == ItemType.Drive)
{
// Get Details for Drive
DriveInfo drInfo = new DriveInfo(Path);
Details.Add("Drive Type", drInfo.DriveType.ToString());
Details.Add("Drive Format", drInfo.DriveFormat.ToString());
Details.Add("Volume Label", drInfo.VolumeLabel.ToString());
}
else if (Type == ItemType.Directory)
{
// Get details for Directory
DirectoryInfo dirInfo = new DirectoryInfo(Path);
Details.Add("Creation Time",
dirInfo.CreationTime.ToString("MM-dd-yyyy"));
Details.Add("Last Access Time",
dirInfo.LastAccessTime.ToString("MM-dd-yyyy"));
Details.Add("Last Write Time",
dirInfo.LastWriteTime.ToString("MM-dd-yyyy"));
Details.Add("Root", dirInfo.Root.ToString());
}
else
{
// Get details for file
FileInfo fileInfo = new FileInfo(Path);
Details.Add("Creation Time", fileInfo.CreationTime.ToString());
Details.Add("Last Access Time", fileInfo.LastAccessTime.ToString());
Details.Add("Last Write Time", fileInfo.LastWriteTime.ToString());
Details.Add("Directory Name", fileInfo.DirectoryName.ToString());
}
return Details;
}
catch (Exception)
{
return null;
}
}
`CalculateTotalMemory()` 方法返回元素的总内存。
private void CalculateTotalMemory()
{
if (Utility.IsAccessible(this.Path))
{
if (this.Type==ItemType.Drive)
{
this.TotalMemory = new DriveInfo(this.Path).TotalSize /
Utility.DivisionNumber;
this.FreeMemory = new DriveInfo(this.Path).TotalFreeSpace/
Utility.DivisionNumber;
this.UsedMemory = this.TotalMemory - this.FreeMemory;
}
else
{
this.UsedMemory = this.TotalMemory;
}
}
}
同样,`SortMemoryList()` 方法根据内存对详细信息字典进行排序。其思想是将子项及其相应内存的详细信息保存在 `MemoryList` 属性中。这将根据内存对 `MemoryList` 中的项进行排序。
`Analyzer` 类是一个单例实例,包含一个最重要的函数 `GetMemory()`。此函数遍历项(文件、文件夹或驱动器)的子项并计算它们的总大小。如果子项是文件,则内存就是其在设备上的内存。如果子项是文件夹,则它会递归调用自身。
public AnalyzerAttribute GetMemory(string FullPathName)
{
try
{
if (Utility.IsAccessible(FullPathName))
{
DirectoryInfo dirInfo = new DirectoryInfo(FullPathName);
AnalyzerAttribute attr = new AnalyzerAttribute(FullPathName);
foreach (FileInfo file in dirInfo.GetFiles())
{
attr.MemoryList.Add(new FileMemoryList()
{
FileName = file.Name,
Memory = file.Length / Utility.DivisionNumber
});
attr.TotalMemory += file.Length / Utility.DivisionNumber;
}
foreach (string fileName in Directory.GetDirectories(FullPathName))
{
double memory = GetMemory(fileName).TotalMemory;
attr.MemoryList.Add(new FileMemoryList()
{
FileName = fileName,
Memory = memory
});
attr.TotalMemory += memory;
}
return attr;
}
else
{
return new AnalyzerAttribute(FullPathName)
{
MemoryList = new List<FileMemoryList>(){new FileMemoryList()
{FileName=FullPathName,Memory=0}}
};
}
}
catch (Exception)
{
// log exception
return null;
}
}
`DeviceIO` 类保存有关任何特定 IO 设备的信息 - 即文件、文件夹或驱动器。它有一个名为 `GetChildren()` 的方法,该方法递归地获取子项。其他属性,如 Name、Path 等,都是不言自明的。
public List<DeviceIO> GetChildren(string Path)
{
try
{
//DeviceIO parent = new DeviceIO(Path)
//{ Name = System.IO.Path.GetFileName(Path) };
List<DeviceIO> children = new List<DeviceIO>();
foreach (string name in Directory.GetDirectories(Path))
{
if(Utility.IsAccessible(name))
children.Add(new DeviceIO(name)
{
Name = System.IO.Path.GetFileName(name),
Path = Path
});
}
foreach (string file in Directory.GetFiles(Path))
{
children.Add(new DeviceIO(file)
{
Name = System.IO.Path.GetFileName(file),
Path = Path
});
}
return children;
}
catch (Exception)
{
return null;
}
}
}
第二部分 - 模型
模型基本上充当框架的包装器,并在 `ViewModel` 调用时将框架封装为一个黑盒。
`AnalyzerModel` 类具有以下属性 - `Path`、`Name`、`ElementType`、`TotalMemory`。`AccessList` 属性提供特定文件夹的访问控制详细信息。这是通过从框架调用来实现的。
public DataTable GetFolderGroups(string folderPath)
{
DataTable ACLTable = new DataTable();
DataColumn[] dc = { new DataColumn("Identity"),
new DataColumn("Control"), new DataColumn("Rights") };
ACLTable.Columns.AddRange(dc);
FileSecurity fs = System.IO.File.GetAccessControl(folderPath);
AuthorizationRuleCollection arc = fs.GetAccessRules
(true, true, typeof(NTAccount));
foreach (FileSystemAccessRule fsar in arc)
{
//ignore everyone
if (fsar.IdentityReference.Value.ToLower() == "everyone")
continue;
//ignore BUILTIN
if (fsar.IdentityReference.Value.ToLower().StartsWith("builtin"))
continue;
if (fsar.IdentityReference.Value.ToUpper() == @"NT AUTHORITY\SYSTEM")
continue;
DataRow row = ACLTable.NewRow();
string group = fsar.IdentityReference.Value;
int nindex = group.IndexOf('\\');
if (nindex > 0)
{
row["Identity"] = group.Substring(nindex + 1,
group.Length - nindex - 1);
//Debug.WriteLine(row["Identity"]);
row["Control"] = fsar.AccessControlType.ToString();
//Debug.WriteLine(row["AcceptControlType"]);
row["Rights"] = fsar.FileSystemRights.ToString();
//Debug.WriteLine(row["FileSystemRights"]);
ACLTable.Rows.Add(row);
}
}
return ACLTable;
}
`DefineMemory`(double memory) 将原始内存数据转换为可呈现的格式。原始数据始终以字节为单位。它将其转换为适用的单位并进行四舍五入。
private string DefineMemory(double memory)
{
try
{
int count=1;
string unit = string.Empty;
do
{
memory = System.Math.Round(memory / System.Math.Pow(1024, count),2);
count += 1;
} while (System.Math.Round(memory / 1024, 2) > 1);
if(count==0)
unit = "Bytes";
else if(count==1)
unit = "KB";
else if(count==2)
unit = "MB";
else
unit = "GB";
return memory + " " + unit;
}
catch (Exception)
{
throw;
}
}
}
利用这些,模型按如下方式填充其属性:
this.Path = _attr.Path;
this.Name = _attr.Name;
this.ElementType = _attr.Type.ToString();
this.TotalMemory = DefineMemory(_attr.TotalMemory);
this.UsedMemory = DefineMemory(_attr.UsedMemory);
this.History = _attr.GetDetails();
this.InaccssibleList = _attr.InaccessibleList;
AccessList = _analyzer.GetFolderGroups(Path);
模型现在已准备好供 `ViewModel` 使用。
第三部分 – ViewModel
`ViewModelBase` 类是所有 `ViewModels` 的基类。它继承自 `DispatcherObject`(支持异步调用)、`INotifyPropertyChanged` 接口和 `IDisposable` 接口。
`Dispose` 功能实现如下:
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// dispose managed resources
}
// dispose unmanaged resources
}
~ViewModelBase()
{
Dispose(false);
}
`INotifyPropertyChanged` 实现如下:
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string PropertyName)
{
if(this.PropertyChanged!=null)
this.PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
`ViewModelBase` 还必须支持集合更改事件。对于任何具有 `Observable` 集合属性并继承自 `ViewModelBase` 的视图模型,都必须为添加到集合中的项实现 `INotifyPropertyChanged`。这是因为,虽然 `Observable` 集合实现了 `INotifyCollectionChanged`,但它们默认不隐式实现 `INotifyPropertyChanged`。
protected virtual void Item_CollectionChanged
(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action.ToString() == "Add")
{
if (e.NewItems != null)
{
foreach (ViewModelBase item in e.NewItems)
{
item.PropertyChanged +=
new PropertyChangedEventHandler(Item_PropertyChanged);
}
}
}
else
{
if (e.OldItems != null)
{
foreach (ViewModelBase item in e.OldItems)
{
item.PropertyChanged -=
new PropertyChangedEventHandler(Item_PropertyChanged);
}
}
}
}
protected virtual void Item_PropertyChanged
(object sender, PropertyChangedEventArgs e)
{
if (e != null)
this.OnPropertyChanged(e.PropertyName);
}
protected virtual void OnPropertyChanged(string PropertyName)
{
if(this.PropertyChanged!=null)
this.PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
工作区是我们视图中工作区的占位符。工作区可以被视为表示层中的一个独立演示者。它也继承自 `ViewModelBase`。
public class WorkspaceViewModel:ViewModelBase
{
public event EventHandler RequestClose;
public WorkspaceViewModel()
{
}
protected virtual void OnRequestClose()
{
if (this.RequestClose != null)
this.RequestClose(this, new EventArgs());
}
protected virtual bool OnRequestCanClose()
{
return true;
}
}
`EntityViewModel` 类是视图中主要演示的占位符。它有一个 `MemoryPercentage` 属性,该属性是一个字典项,包含项目名称及其内存的键值对。它从模型加载为:
private void InitializeGraph()
{
//model = new AnalyzerModel(Path);
memoryList = new List<ModelFileMemoryList>();
memoryPercentage = new Dictionary<string, double>();
memoryList = model.MemoryList;
foreach (ModelFileMemoryList item in model.MemoryList)
{
string unit = item.ItemMemory.Split(' ')[1];
double memory = Convert.ToDouble(item.ItemMemory.Split(' ')[0]);
if (unit == "KB")
memory = memory / (1024 * 1024);
else if (unit == "MB")
memory = memory / 1024;
unit = model.TotalMemory.ToString().Split(' ')[1];
double totalMemory = Convert.ToDouble
(model.TotalMemory.ToString().Split(' ')[0]);
if (unit == "KB")
totalMemory = memory / (1024 * 1024);
else if (unit == "MB")
totalMemory = memory / 1024;
double percentage = System.Math.Round((memory / totalMemory) * 100, 2);
this.MemoryPercentage.Add(System.IO.Path.GetFileName
(item.FileName), percentage);
}
this.AccessList = model.AccessList;
}
`MyComputerTreeViewModel` 是“我的电脑”演示元素的视图模型。它在树状视图中列出了所选元素的子项。
所有这些之上是 `MainWindowViewModel`。此 `ViewModel` 直接继承自 `WorkSpaceViewModel`,并充当所有其他视图模型的占位符。
第四部分 - 视图
每个视图都是一个单独的用户控件,它们从 `MainWindowView` 启动,这是 WPF 主应用程序项目。
有三个主要的视图值得描述:
- `EntityView` – `EntityView` 包含核心信息。它在 `Listbox` 中列出子项,该 `Listbox` 显示 `AnalyzerItemView`。
<Border Grid.Row="1" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="1" BorderBrush="Bisque" BorderThickness="1" Margin="2,2,2,2" VerticalAlignment="Stretch"> <ScrollViewer Background="#FFF8F8F8" MaxHeight="400"> <ItemsControl ItemsSource="{Binding Items}" Background="Transparent" BorderBrush="Transparent" ItemTemplate="{StaticResource TreeViewDataTemplate}" FontSize="15" VerticalAlignment="Top" FontFamily="Buxton Sketch" FontWeight="Bold" Margin="10,10,10,10"/> </ScrollViewer> </Border>
图表绑定到视图模型的 `MemoryPercentage` 属性。我使用了 WPF 图表工具包中的一个简单饼图。请注意 `DependentValuePath` 和 `IndependentValuePath` 属性的使用。
<chartingToolkit:Chart DataContext="{Binding}" HorizontalAlignment="Stretch" Margin="2,2,2,2" Name="EntityChart" Grid.Column="1" Grid.Row="2" Grid.ColumnSpan="2" BorderBrush="BurlyWood" Title="Graphical Analysis" VerticalAlignment="Stretch" Background="Silver" FontFamily="Buxton Sketch" FontSize="15"> <chartingToolkit:Chart.TitleStyle> <Style TargetType="datavis:Title"> <Setter Property="FontSize" Value="30"/> <Setter Property="HorizontalAlignment" Value="Center"/> </Style> </chartingToolkit:Chart.TitleStyle> <chartingToolkit:PieSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding MemoryPercentage}"/>
- `MyComputerView` – 用于显示父元素的子元素。它使用树状视图控件,并使用如下所示的分层数据模板绑定其子项。它还对不言自明的属性(如 `IsExpanded`、`IsSelected` 等)进行双向绑定。
<TreeView ItemsSource="{Binding FirstGeneration}" Background="Transparent" BorderBrush="Transparent" ToolTip="Select an item and click the button" > <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> <Setter Property="FontWeight" Value="Normal" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="FontWeight" Value="Bold" /> </Trigger> </Style.Triggers> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <DockPanel Style="{StaticResource DockPanelStyle}" > <TextBlock Text="{Binding Name}" FontFamily="Buxton Sketch" FontSize="20" Style="{StaticResource TextBlockStyle}" ToolTip="{Binding ToolTipInformation}"> </TextBlock> </DockPanel> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView>
- `MainWindowView` – 可以称之为其他视图的启动板。`EntityView` 被启动为:
<Border BorderBrush="Brown" BorderThickness="0" Grid.Column="1" Margin="2,2,2,2" Grid.Row="1"> <TabControl ItemsSource="{Binding WorkSpaces}" VerticalAlignment="Stretch" ItemTemplate="{StaticResource ClosableTabItemTemplate}" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding SelectedItem}"/> </Border>
同样,`MyComputerView` 被启动为:
<StackPanel Grid.Row="1" DataContext="{Binding}" Background="Transparent"> <Separator Background="SlateGray" HorizontalAlignment="Stretch"/> <ItemsControl Background="Transparent" ItemsSource="{Binding MyComputer}"/> </StackPanel>
结束语
这个工具有很大的改进和增加功能的空间。如果您有兴趣做出贡献,请写信给我,或访问 - http://dart.codeplex.com/。
您可能会发现以下链接很有用。您的反馈和贡献将不胜感激。