I have created it using winforms by just adding permon classes and downloading some realtime dasboards but i find it quite difficult in WPF
-
1Well, you have to know WPF. But yes, it's possible, and the result will probably be better than in Winforms. – Robert Harvey Aug 21 '20 at 18:54
-
It takes time to really learn wpf... but it can be fun. Just gotta stay with it. – Kixoka Aug 21 '20 at 18:55
-
or atleast any hint , how i can use winforms controls in WPF ?so i will use perfmoncounter from toolbox – user8758891 Aug 21 '20 at 19:04
-
May be helpful: https://stackoverflow.com/questions/278071/how-to-get-the-cpu-usage-in-c – TimonYang Aug 24 '20 at 08:12
1 Answers
There's a lot of tools that allows to draw various graphs in WPF.
But since i didn't find any manual graph drawing implementation, I've created the example - how to draw a graph in WPF using MVVM programming pattern.
0. Helper classes
For proper and easy MVVM implementation I will use 2 following classes.
NotifyPropertyChanged.cs - to notify UI on changes.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
RelayCommand.cs - for easy commands use (for Button
)
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute) = (execute, canExecute);
public bool CanExecute(object parameter)
=> _canExecute == null || _canExecute(parameter);
public void Execute(object parameter)
=> _execute(parameter);
}
1. Data implementation
As graph consists of contant amount of points an they're just turning around, I've implemented the Round-Robin Collection that has only one method Push()
.
RoundRobinCollection.cs
public class RoundRobinCollection : NotifyPropertyChanged
{
private readonly List<float> _values;
public IReadOnlyList<float> Values => _values;
public RoundRobinCollection(int amount)
{
_values = new List<float>();
for (int i = 0; i < amount; i++)
_values.Add(0F);
}
public void Push(float value)
{
_values.RemoveAt(0);
_values.Add(value);
OnPropertyChanged(nameof(Values));
}
}
2. Values collection to Polygon points Converter
Used in View markup
PolygonConverter.cs
public class PolygonConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
PointCollection points = new PointCollection();
if (values.Length == 3 && values[0] is IReadOnlyList<float> dataPoints && values[1] is double width && values[2] is double height)
{
points.Add(new Point(0, height));
points.Add(new Point(width, height));
double step = width / (dataPoints.Count - 1);
double position = width;
for (int i = dataPoints.Count - 1; i >= 0; i--)
{
points.Add(new Point(position, height - height * dataPoints[i] / 100));
position -= step;
}
}
return points;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => null;
}
3. View Model
The main class containing all business logic of the App
public class MainViewModel : NotifyPropertyChanged
{
private bool _graphEnabled;
private float _lastCpuValue;
private ICommand _enableCommand;
public RoundRobinCollection ProcessorTime { get; }
public string ButtonText => GraphEnabled ? "Stop" : "Start";
public bool GraphEnabled
{
get => _graphEnabled;
set
{
if (value != _graphEnabled)
{
_graphEnabled = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ButtonText));
if (value)
ReadCpu();
}
}
}
public float LastCpuValue
{
get => _lastCpuValue;
set
{
_lastCpuValue = value;
OnPropertyChanged();
}
}
public ICommand EnableCommand => _enableCommand ?? (_enableCommand = new RelayCommand(parameter =>
{
GraphEnabled = !GraphEnabled;
}));
private async void ReadCpu()
{
try
{
using (PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"))
{
while (GraphEnabled)
{
LastCpuValue = cpuCounter.NextValue();
ProcessorTime.Push(LastCpuValue);
await Task.Delay(1000);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public MainViewModel()
{
ProcessorTime = new RoundRobinCollection(100);
}
}
Disclamer: async void
is not recommended approach to make something async
but here the usage is safe because any possible Exception
will be handled inside. For more information about why async void
is bad, refer to the documentation - Asynchronous Programming.
4. View
The whole UI markup of the app
<Window x:Class="CpuUsageExample.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:CpuUsageExample"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="800" >
<Window.DataContext>
<local:MainViewModel/><!-- attach View Model -->
</Window.DataContext>
<Window.Resources>
<local:PolygonConverter x:Key="PolygonConverter"/><!-- attach Converter -->
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<WrapPanel>
<Button Margin="5" Padding="10,0" Content="{Binding ButtonText}" Command="{Binding EnableCommand}"/>
<TextBlock Margin="5" Text="CPU:"/>
<TextBlock Margin="0, 5" Text="{Binding LastCpuValue, StringFormat=##0.##}" FontWeight="Bold"/>
<TextBlock Margin="0, 5" Text="%" FontWeight="Bold"/>
</WrapPanel>
<Border Margin="5" Grid.Row="1" BorderThickness="1" BorderBrush="Gray" SnapsToDevicePixels="True">
<Canvas ClipToBounds="True">
<Polygon Stroke="CadetBlue" Fill="AliceBlue">
<Polygon.Resources>
<Style TargetType="Polygon">
<Setter Property="Points">
<Setter.Value>
<MultiBinding Converter="{StaticResource PolygonConverter}">
<Binding Path="ProcessorTime.Values"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Polygon.Resources>
</Polygon>
</Canvas>
</Border>
</Grid>
</Window>
P.S. There's no code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

- 4,558
- 2
- 12
- 24
-
1
-
I am trying to add x (CPU) and y(dates for atleast 3-4 intervals) co-rdinates in this graph however no luck !!! – user8758891 Sep 02 '20 at 13:37
-
@user8758891 You may draw the grid on underlying Canvas ([example](https://stackoverflow.com/a/63495171/12888024)) – aepot Sep 02 '20 at 13:38
-
@user8758891 Adding text isn't a trivial job because you probably need to modify the RRC to store the timestamp of the each value. Then add some `ItemsControl` under the `Canvas` and bind it to the same RRC through another converter, or create some additional `ObservableCollection` what will contain coordinates for each `TextBlock` and text inside. Then modify the both collections as once. – aepot Sep 02 '20 at 13:50
-
@user8758891 did I answered the question? Did you check the [link](https://stackoverflow.com/a/31537844/12888024) at the top of the answer? If the question was answered, please accept the answer. If not, I'll delete the answer as unhelpful because I have no more ideas. – aepot Sep 02 '20 at 16:31
-
1