private void BeforeTreeViewExpand(object sender, TreeViewCancelEventArgs args)
        {
            performanceTreeView.BeginUpdate();

            TreeNode node = args.Node;

            if (node.Nodes.Count == 0 || "replace me".Equals(node.Nodes[0].Tag))
            {
                node.Nodes.Clear();

                if (node.Tag is Performance.Group)
                {
                    Performance.Group group = (Performance.Group)node.Tag;
                    for (int i = 0; i < group.Files.Count; i++)
                    {
                        TreeNode fileNode = args.Node.Nodes.Add("Datei " + (i + 1) + ": " + Units.FormatTime(group.Files[i].Time(true, true)));
                        fileNode.Tag = group.Files[i];
                        fileNode.Nodes.Add("Loading...").Tag = "replace me";
                    }
                }
                else if (node.Tag is Performance.File)
                {
                    Performance.File file = (Performance.File)node.Tag;
                    for (int i = 0; i < file.Chunks.Count; i++)
                    {
                        TreeNode chunkNode = node.Nodes.Add("Chunk " + (i + 1) + ": " + Units.FormatTime(file.Chunks[i].Time));
                        chunkNode.Tag = file.Chunks[i];
                    }
                }
            }

            performanceTreeView.EndUpdate();
        }
        private void UpdateInfo(Performance.File file)
        {
            string[] details =
            {
                "Gesamtdauer: " + Units.FormatTime(file.Time(true,                                   true)),
                "Größe: " + Units.FormatSize(file.FileSize) + " (" + file.Chunks.Count + " Chunks)",
                "",
                "Übertragungsrate: " + Units.FormatRate(file.TransmissionRate(true,                  true)),
                "... ohne Verzögerung vor Stream: " + Units.FormatRate(file.TransmissionRate(false,  true)),
                "... ohne Verzögerung nach Stream: " + Units.FormatRate(file.TransmissionRate(true,  false)),
                "... ohne Verzögerungen: " + Units.FormatRate(file.TransmissionRate(false,           false)),
                "",
                "Verzögerung vor Stream: " + Units.FormatTime(file.StreamStartTime),
                "Verzögerung nach Stream: " + Units.FormatTime(file.EndTime - file.StreamEndTime),
                "",
                file.StartEntry.SourcePath + " -> " + file.StartEntry.TargetPath
            };
            detailsTextBox.Text = string.Join("\r\n", details);

            performanceGraphView.CreateGraph(file);
        }
        public void CreateGraph(Performance.File file)
        {
            PointF[] points     = new PointF[file.Chunks.Count + 3];
            int      pointIndex = 0;

            points[pointIndex++] = new PointF(0, 0);
            points[pointIndex++] = new PointF(file.StreamStartTime, 0);

            foreach (Performance.Chunk chunk in file.Chunks)
            {
                points[pointIndex++] = new PointF(chunk.TimeSinceFileStart, chunk.BytesTransferred);
            }

            points[pointIndex] = new PointF(file.EndTime, file.FileSize);

            Axis axisX = new Axis()
            {
                Range  = new Range(0, file.Time(true, true)),
                Label  = "Zeit",
                Format = (f) => "" + Units.FormatTime((ulong)f)
            };

            axisX.Markers.Add(file.StreamStartTime, Color.Red);
            axisX.Markers.Add(file.StreamEndTime, Color.Purple);

            Axis axisY = new Axis()
            {
                Range  = new Range(0, file.FileSize),
                Label  = "Größe",
                Format = (f) => Units.FormatSize(f)
            };

            graph = new Graph()
            {
                Points = points, AxisX = axisX, AxisY = axisY
            };
            Refresh();
        }