Skip to main content

WPF C#/VB
How to Analyse Storage on Your PC

C#

// AZUL CODING ---------------------------------------
// WPF C#/VB - How to Analyse Storage on Your PC
// https://youtu.be/M-V2GKO7fh4


// Looking for an example app? Get Quota Express:
// https://johnjds.co.uk/quota?ref=azul

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

// Plugin: https://www.nuget.org/packages/WindowsAPICodePackShell
using Microsoft.WindowsAPICodePack.Dialogs;
using Microsoft.WindowsAPICodePack.Shell;

namespace AzulStorageAnalysis
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ObservableCollection<FileItem> FileDisplayList = [];
        private CancellationTokenSource CancelTokenSource = new();
        private Task? FileSizeTask;
        private string CurrentRoot = "";

        private readonly CommonOpenFileDialog FolderBrowserDialog = new()
        {
            IsFolderPicker = true,
            Multiselect = false
        };

        public MainWindow()
        {
            InitializeComponent();

            FolderItems.ItemsSource = FileDisplayList;
            DriveCombo.ItemsSource = DriveInfo.GetDrives().Select(d =>
            {
                string displayName = d.DriveType switch
                {
                    DriveType.Removable => "USB Drive " + d.Name,
                    DriveType.Network => "Network Drive " + d.Name,
                    DriveType.CDRom => "CD Drive " + d.Name,
                    _ => "Drive " + d.Name
                };

                return new ComboBoxItem()
                {
                    Content = displayName,
                    Tag = d.Name
                };
            });

            DriveCombo.SelectedIndex = 0;
            GetFiles(KnownFolders.Downloads?.Path ?? "");
        }

        #region Folder Picker

        private void ChooseBtn_Click(object sender, RoutedEventArgs e)
        {
            if (FolderBrowserDialog.ShowDialog() == CommonFileDialogResult.Ok)
                GetFiles(FolderBrowserDialog.FileName ?? "");
        }

        private void FilePathTxt_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter && !string.IsNullOrWhiteSpace(FilePathTxt.Text))
            {
                if (Path.Exists(FilePathTxt.Text))
                {
                    GetFiles(FilePathTxt.Text);
                }
                else
                {
                    MessageBox.Show("The file path you entered doesn't exist.");
                    FilePathTxt.Text = CurrentRoot;
                }
            }
        }

        #endregion
        #region File Items

        private void FileItem_Click(object sender, RoutedEventArgs e)
        {
            FileItem item = (FileItem)((Button)sender).Tag;

            if (item.IsFolder)
            {
                GetFiles(item.FilePath);
            }
            else
            {
                try
                {
                    _ = Process.Start(new ProcessStartInfo()
                    {
                        FileName = item.FilePath,
                        UseShellExecute = true
                    });
                }
                catch
                {
                    MessageBox.Show("Can't open file.");
                }
            }
        }

        private async void GetFiles(string path)
        {
            try
            {
                string[] folders = Directory.GetDirectories(path);
                string[] files = Directory.GetFiles(path);

                if (FileSizeTask != null && !FileSizeTask.IsCompleted)
                {
                    CancelTokenSource.Cancel();
                    await FileSizeTask;
                }

                CancelTokenSource = new();
                FileDisplayList.Clear();

                foreach (string item in folders)
                    FileDisplayList.Add(new FileItem()
                    {
                        FileName = Path.GetFileName(item),
                        FilePath = item,
                        IsFolder = true
                    });

                foreach (string item in files)
                    FileDisplayList.Add(new FileItem()
                    {
                        FileName = Path.GetFileName(item),
                        FilePath = item,
                        IsFolder = false
                    });

                FilePathTxt.Text = CurrentRoot = path;
                FolderSizeLbl.Text = "Calculating...";
                Scroller.ScrollToTop();

                FileSizeTask = Task.Run(() => GetFileSizes(CancelTokenSource.Token));
                await FileSizeTask;

                if (!FileSizeTask.IsCanceled)
                    FolderSizeLbl.Text = FormatBytes(FileDisplayList.Sum(x => x.FileSize));
            }
            catch (Exception ex)
            {
                MessageBox.Show("An error occurred: " + ex.Message);
            }
        }

        private void GetFileSizes(CancellationToken cancellationToken)
        {
            foreach (FileItem item in FileDisplayList)
            {
                if (cancellationToken.IsCancellationRequested)
                    return;

                try
                {
                    if (item.IsFolder)
                        item.FileSize = GetDirSize(new DirectoryInfo(item.FilePath), cancellationToken);
                    else
                        item.FileSize = new FileInfo(item.FilePath).Length;
                }
                catch
                {
                    item.FileSize = 0L;
                }
            }
        }

        #endregion
        #region Drive Analysis

        private void DriveCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                string driveName = (string)((ComboBoxItem)DriveCombo.SelectedValue).Tag;
                DriveInfo d = new(driveName);

                DriveSpaceTakenLbl.Text = FormatBytes(d.TotalSize - d.TotalFreeSpace);
                DriveRemainingSpaceLbl.Text = FormatBytes(d.TotalFreeSpace);
                DriveTotalSpaceLbl.Text = FormatBytes(d.TotalSize);
            }
            catch
            {
                DriveSpaceTakenLbl.Text = DriveRemainingSpaceLbl.Text = DriveTotalSpaceLbl.Text = "—";
            }
        }

        #endregion
        #region Helper Functions

        private static long GetDirSize(DirectoryInfo d, CancellationToken cancellationToken)
        {
            long size = 0L;
            try
            {
                FileInfo[] fis = d.GetFiles();
                foreach (FileInfo fi in fis)
                {
                    if (cancellationToken.IsCancellationRequested)
                        return 0L;

                    size += fi.Length;
                }

                DirectoryInfo[] dis = d.GetDirectories();
                foreach (DirectoryInfo di in dis)
                {
                    if (cancellationToken.IsCancellationRequested)
                        return 0L;

                    size += GetDirSize(di, cancellationToken);
                }
            }
            catch { }

            if (cancellationToken.IsCancellationRequested)
                return 0L;
            else
                return size;
        }

        public static string FormatBytes(long bytes)
        {
            double bytesConverted;
            string result = "—";

            try
            {
                switch (bytes)
                {
                    case >= 1125899906842625:
                        result = "1000+ TB";
                        break;
                    case >= 1099511627776:
                        bytesConverted = bytes / (double)1099511627776;
                        result = Math.Round(bytesConverted, 2).ToString() + " TB";
                        break;
                    case >= 1073741824:
                        bytesConverted = bytes / (double)1073741824;
                        result = Math.Round(bytesConverted, 2).ToString() + " GB";
                        break;
                    case >= 1048576:
                        bytesConverted = bytes / (double)1048576;
                        result = Math.Round(bytesConverted, 2).ToString() + " MB";
                        break;
                    case >= 1024:
                        bytesConverted = bytes / (double)1024;
                        result = Math.Round(bytesConverted, 2).ToString() + " KB";
                        break;
                    case >= 1:
                        bytesConverted = bytes;
                        result = Math.Round(bytesConverted, 2).ToString() + " b";
                        break;
                    default:
                        return result;
                }

                return result;
            }
            catch
            {
                return result;
            }
        }

        #endregion
    }

    #region Helper Classes

    public class FileItem : INotifyPropertyChanged
    {
        public string FileName { get; set; } = "";
        public string FilePath { get; set; } = "";

        public bool IsFolder { get; set; } = false;

        private long size = -1L;
        public long FileSize
        {
            get { return size; }
            set
            {
                if (size != value)
                {
                    size = value;
                    OnPropertyChanged(nameof(FileSize));
                }
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class IconConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ?
                "https://img.icons8.com/fluency/48/folder-invoices--v1.png" :
                "https://img.icons8.com/fluency/48/file.png";
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return true;
        }
    }

    public class FileSizeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (long)value == -1 ? "Calculating..." : MainWindow.FormatBytes((long)value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return 0L;
        }
    }

    #endregion
}

Enjoying this tutorial?


VB.NET

' AZUL CODING ---------------------------------------
' WPF C#/VB - How to Analyse Storage on Your PC
' https://youtu.be/M-V2GKO7fh4


' Looking for an example app? Get Quota Express:
' https://johnjds.co.uk/quota?ref=azul

Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Globalization
Imports System.IO
Imports System.Threading

' Plugin: https://www.nuget.org/packages/WindowsAPICodePackShell
Imports Microsoft.WindowsAPICodePack.Dialogs
Imports Microsoft.WindowsAPICodePack.Shell

Class MainWindow
    Inherits Window

    Private ReadOnly FileDisplayList As New ObservableCollection(Of FileItem)
    Private CancelTokenSource As New CancellationTokenSource()
    Private FileSizeTask As Task
    Private CurrentRoot As String = ""

    Private ReadOnly FolderBrowserDialog As New CommonOpenFileDialog() With {
        .IsFolderPicker = True,
        .Multiselect = False
    }

    Public Sub New()
        InitializeComponent()

        FolderItems.ItemsSource = FileDisplayList
        DriveCombo.ItemsSource = DriveInfo.GetDrives().Select(
            Function(d)
                Dim displayName As String = ""
                Select Case d.DriveType
                    Case DriveType.Removable
                        displayName = "USB Drive " & d.Name
                    Case DriveType.Network
                        displayName = "Network Drive " & d.Name
                    Case DriveType.CDRom
                        displayName = "CD Drive " & d.Name
                    Case Else
                        displayName = "Drive " & d.Name
                End Select

                Return New ComboBoxItem() With {
                    .Content = displayName,
                    .Tag = d.Name
                }
            End Function
        )

        DriveCombo.SelectedIndex = 0
        GetFiles(KnownFolders.Downloads.Path)

    End Sub

#Region "Folder Picker"

    Private Sub ChooseBtn_Click(sender As Object, e As RoutedEventArgs)

        If FolderBrowserDialog.ShowDialog() = CommonFileDialogResult.Ok Then
            GetFiles(FolderBrowserDialog.FileName)
        End If

    End Sub

    Private Sub FilePathTxt_PreviewKeyDown(sender As Object, e As KeyEventArgs)

        If e.Key = Key.Enter AndAlso Not String.IsNullOrWhiteSpace(FilePathTxt.Text) Then
            If Path.Exists(FilePathTxt.Text) Then
                GetFiles(FilePathTxt.Text)
            Else
                MessageBox.Show("The file path you entered doesn't exist.")
                FilePathTxt.Text = CurrentRoot
            End If
        End If

    End Sub

#End Region
#Region "File Items"

    Private Sub FileItem_Click(sender As Object, e As RoutedEventArgs)
        Dim item As FileItem = DirectCast(DirectCast(sender, Button).Tag, FileItem)

        If item.IsFolder Then
            GetFiles(item.FilePath)
        Else
            Try
                Process.Start(New ProcessStartInfo() With {
                    .FileName = item.FilePath,
                    .UseShellExecute = True
                })
            Catch
                MessageBox.Show("Can't open file.")
            End Try
        End If

    End Sub

    Private Async Sub GetFiles(path As String)
        Try
            Dim folders As String() = Directory.GetDirectories(path)
            Dim files As String() = Directory.GetFiles(path)

            If FileSizeTask IsNot Nothing AndAlso Not FileSizeTask.IsCompleted Then
                CancelTokenSource.Cancel()
                Await FileSizeTask
            End If

            CancelTokenSource = New CancellationTokenSource()
            FileDisplayList.Clear()

            For Each item In folders
                FileDisplayList.Add(New FileItem() With {
                    .FileName = IO.Path.GetFileName(item),
                    .FilePath = item,
                    .IsFolder = True
                })
            Next

            For Each item In files
                FileDisplayList.Add(New FileItem() With {
                    .FileName = IO.Path.GetFileName(item),
                    .FilePath = item,
                    .IsFolder = False
                })
            Next

            CurrentRoot = path
            FilePathTxt.Text = CurrentRoot
            FolderSizeLbl.Text = "Calculating..."
            Scroller.ScrollToTop()

            FileSizeTask = Task.Run(Sub() GetFileSizes(CancelTokenSource.Token))
            Await FileSizeTask

            If Not FileSizeTask.IsCanceled Then
                FolderSizeLbl.Text = FormatBytes(FileDisplayList.Sum(Function(x) x.FileSize))
            End If

        Catch ex As Exception
            MessageBox.Show("An error occurred: " & ex.Message)
        End Try

    End Sub

    Private Sub GetFileSizes(cancellationToken As CancellationToken)

        For Each item In FileDisplayList
            If cancellationToken.IsCancellationRequested Then
                Return
            End If

            Try
                If item.IsFolder Then
                    item.FileSize = GetDirSize(New DirectoryInfo(item.FilePath), cancellationToken)
                Else
                    item.FileSize = New FileInfo(item.FilePath).Length
                End If
            Catch
                item.FileSize = 0L
            End Try
        Next

    End Sub

#End Region
#Region "Drive Analysis"

    Private Sub DriveCombo_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
        Try
            Dim driveName As String = DriveCombo.SelectedValue.Tag
            Dim d As New DriveInfo(driveName)

            DriveSpaceTakenLbl.Text = FormatBytes(d.TotalSize - d.TotalFreeSpace)
            DriveRemainingSpaceLbl.Text = FormatBytes(d.TotalFreeSpace)
            DriveTotalSpaceLbl.Text = FormatBytes(d.TotalSize)
        Catch
            DriveSpaceTakenLbl.Text = "—"
            DriveRemainingSpaceLbl.Text = "—"
            DriveTotalSpaceLbl.Text = "—"
        End Try

    End Sub

#End Region
#Region "Helper Functions"

    Private Shared Function GetDirSize(d As DirectoryInfo, cancellationToken As CancellationToken) As Long

        Dim size As Long = 0L
        Try
            Dim fis As FileInfo() = d.GetFiles()
            For Each fi In fis
                If cancellationToken.IsCancellationRequested Then
                    Return 0L
                End If

                size += fi.Length
            Next

            Dim dis As DirectoryInfo() = d.GetDirectories()
            For Each di In dis
                If cancellationToken.IsCancellationRequested Then
                    Return 0L
                End If

                size += GetDirSize(di, cancellationToken)
            Next
        Catch
        End Try

        If cancellationToken.IsCancellationRequested Then
            Return 0L
        Else
            Return size
        End If

    End Function

    Public Shared Function FormatBytes(bytes As Long) As String
        Dim bytesConverted As Double
        Dim result As String = "—"

        Try
            Select Case bytes
                Case >= 1125899906842625L
                    result = "1000+ TB"
                Case >= 1099511627776L
                    bytesConverted = bytes / 1099511627776.0
                    result = Math.Round(bytesConverted, 2).ToString() & " TB"
                Case >= 1073741824L
                    bytesConverted = bytes / 1073741824.0
                    result = Math.Round(bytesConverted, 2).ToString() & " GB"
                Case >= 1048576L
                    bytesConverted = bytes / 1048576.0
                    result = Math.Round(bytesConverted, 2).ToString() & " MB"
                Case >= 1024L
                    bytesConverted = bytes / 1024.0
                    result = Math.Round(bytesConverted, 2).ToString() & " KB"
                Case >= 1L
                    bytesConverted = bytes
                    result = Math.Round(bytesConverted, 2).ToString() & " b"
                Case Else
                    Return result
            End Select

            Return result
        Catch
            Return result
        End Try

    End Function

#End Region
End Class

#Region "Helper Classes"

Public Class FileItem
    Implements INotifyPropertyChanged

    Public Property FileName As String = ""
    Public Property FilePath As String = ""

    Public Property IsFolder As Boolean = False

    Private _size As Long = -1L
    Public Property FileSize As Long
        Get
            Return _size
        End Get
        Set(value As Long)
            If _size <> value Then
                _size = value
                OnPropertyChanged(NameOf(FileSize))
            End If
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Overridable Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

Public Class IconConverter
    Implements IValueConverter

    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        Return If(value,
            "https://img.icons8.com/fluency/48/folder-invoices--v1.png",
            "https://img.icons8.com/fluency/48/file.png")
    End Function

    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return True
    End Function
End Class

Public Class FileSizeConverter
    Implements IValueConverter

    Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        Return If(value = -1, "Calculating...", MainWindow.FormatBytes(value))
    End Function

    Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return 0L
    End Function
End Class

#End Region

XAML

<!-- AZUL CODING --------------------------------------- -->
<!-- WPF C#/VB - How to Analyse Storage on Your PC -->
<!-- https://youtu.be/M-V2GKO7fh4 -->


<!-- change 'AzulStorageAnalysis' to the name of the project you've set up -->
<Window x:Class="AzulStorageAnalysis.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AzulStorageAnalysis"
        mc:Ignorable="d"
       Title="Azul Coding - Storage Analysis" Width="600" MinWidth="600" ResizeMode="CanResize" Height="425" MinHeight="425">
    <Window.Resources>
        <local:FileSizeConverter x:Key="FileSizeConverter"/>
        <local:IconConverter x:Key="IconConverter"/>
    </Window.Resources>
    <DockPanel Background="White">
        <Label x:Name="TitleLbl" DockPanel.Dock="Top" Content="Storage analysis" Padding="5,0,5,5" Margin="20,20,20,15" FontWeight="SemiBold" FontSize="16" BorderBrush="DodgerBlue" BorderThickness="0,0,0,2"/>
        <Grid Margin="20,0,20,20" DockPanel.Dock="Top">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="7*"/>
                <ColumnDefinition Width="3*" MaxWidth="250"/>
            </Grid.ColumnDefinitions>
            <DockPanel Grid.Column="0">
                <DockPanel DockPanel.Dock="Top">
                    <Button Name="ChooseBtn" Click="ChooseBtn_Click" DockPanel.Dock="Right" Padding="5,0" Margin="5,0,0,0" Background="#f0f0f0" ToolTip="Choose folder">
                        <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                            <Image Height="24" Width="24" Source="https://img.icons8.com/fluency/48/opened-folder.png"/>
                        </StackPanel>
                    </Button>
                    <TextBox x:Name="FilePathTxt" PreviewKeyDown="FilePathTxt_PreviewKeyDown" TextWrapping="Wrap" FontSize="14" Padding="5" VerticalAlignment="Center"/>
                </DockPanel>
                <ScrollViewer x:Name="Scroller" Margin="0,10,0,0" VerticalScrollBarVisibility="Auto">
                    <ItemsControl x:Name="FolderItems">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <VirtualizingStackPanel/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Button Click="FileItem_Click" Tag="{Binding}" ToolTip="{Binding FilePath}" DockPanel.Dock="Right" Padding="5,3" Background="Transparent" BorderThickness="0" HorizontalContentAlignment="Stretch">
                                    <DockPanel VerticalAlignment="Center">
                                        <Image Height="24" Width="24" Source="{Binding Path=IsFolder, Converter={StaticResource IconConverter}}"/>
                                        <TextBlock Text="{Binding Path=FileSize, Converter={StaticResource FileSizeConverter}}" DockPanel.Dock="Right" Margin="0,0,0,2" FontSize="14" FontWeight="SemiBold" VerticalAlignment="Center"/>
                                        <TextBlock Text="{Binding FileName}" Margin="10,0,10,2" FontSize="14" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" />
                                    </DockPanel>
                                </Button>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
            </DockPanel>
            <StackPanel Grid.Column="1" Margin="15,0,0,0" Background="AliceBlue">
                <TextBlock Margin="20,15,20,0" FontSize="14" Text="Total folder size"/>
                <TextBlock x:Name="FolderSizeLbl" Margin="20,0,20,15" FontSize="16" Text="0 MB" FontWeight="SemiBold"/>
                <Rectangle Height="2" Fill="DodgerBlue" Margin="15,0"/>
                <ComboBox x:Name="DriveCombo" SelectionChanged="DriveCombo_SelectionChanged" Margin="15,20,15,0" FontSize="14"/>
                <TextBlock Margin="20,15,20,0" FontSize="14" Text="Space taken"/>
                <TextBlock x:Name="DriveSpaceTakenLbl" Margin="20,0,20,0" FontSize="16" Text="0 MB" FontWeight="SemiBold"/>
                <TextBlock Margin="20,15,20,0" FontSize="14" Text="Remaining space"/>
                <TextBlock x:Name="DriveRemainingSpaceLbl" Margin="20,0,20,0" FontSize="16" Text="0 MB" FontWeight="SemiBold"/>
                <TextBlock Margin="20,15,20,0" FontSize="14" Text="Total space"/>
                <TextBlock x:Name="DriveTotalSpaceLbl" Margin="20,0,20,15" FontSize="16" Text="0 MB" FontWeight="SemiBold"/>
            </StackPanel>
        </Grid>
    </DockPanel>
</Window>